summary: Resolved Supervisor's PM-0001 spec-missing blocker by persisting the user-provided Builder prompt to prompts/persona-builder.md and distilling a system-level integration spec to docs/specs/persona-manager-integration.md. Renumbered Supervisor's task to PM-0000 (Done) so the Builder's seed list PM-0001..PM-0066 keeps its native numbering.


plan: Read all context files listed in the Builder prompt; verify entities and patterns exist where expected; log confirmation. Files: CLAUDE.md, ARCHITECTURE.md, IMPLEMENTATION_STATUS.md, api/src/specialists/specialist.entity.ts, api/src/onboarding/onboarding.entities.ts, api/src/common/entities.ts, api/src/channels/channels.controller.ts, api/src/notifications/notifications.gateway.ts, agent/main.py, agent/prompts_loader.py, frontend/src/lib/api.ts, docs/specs/persona-manager-integration.md.


summary: All context files verified. Key entities confirmed: Specialist (api/src/specialists/specialist.entity.ts:19), OrgSpecialistAssignment (api/src/onboarding/onboarding.entities.ts:57), Conversation (api/src/common/entities.ts:26 — includes role/specialistId/orgId/agentContext fields the brief assembler will need). HMAC mirror target confirmed at api/src/channels/channels.controller.ts:47-62 (boot-guard pattern using createHmac+timingSafeEqual; per-channel verification inline). NotificationsGateway confirmed at api/src/notifications/notifications.gateway.ts:33 — namespace /notifications, rooms 'org:<id>' and 'experts', JWT auth via query token. Prompt loader: agent/prompts_loader.py:82-94 load_prompt(role) keys directly on the role filename — no mode parameter is needed. Setting role='onboarding_meeting' or role='reporting_meeting' on chat calls naturally loads the corresponding prompts/<role>.txt file. Builder principle #9 is satisfied with new files only; no loader code change required. This is captured in docs/specs/persona-manager-integration.md §6.3. No blockers.


plan: Create docs/decisions/persona-manager/ADR-0001-integration-approach.md. Document the chosen integration approach: additive modules in new directories, no existing-code modification, five new tables with FK to existing entities, agent prompts as new files only, reporting decisions write to both meeting_decisions and existing Action entity via documented extension point.


plan: Write migration api/migrations/1746000000010-PersonaManagerInit.ts adding environment_profiles, meetings, meeting_decisions, meeting_priorities, tavus_function_definitions. Mirror RLS pattern from migration 1714000000009-RLS.ts (USING org_id::text = current_setting('app.current_org_id', TRUE) OR setting NULL/''). tavus_function_definitions is global SuperAdmin-managed, no org_id and no RLS. Idempotency: partial unique index on meetings(assignment_id) WHERE kind='onboarding' AND status IN ('scheduled','in_progress'). Reversible via down() that drops policies, triggers, and tables in reverse FK order.


summary: Migration creates five new tables. RLS org_isolation policies installed on the four org-scoped tables matching migration 9 pattern verbatim. tavus_function_definitions has no RLS (global, SuperAdmin-only at controller layer). Idempotency invariant baked in via partial unique index on meetings(assignment_id) WHERE kind='onboarding' AND status IN ('scheduled','in_progress'). Touch-updated-at triggers installed on environment_profiles, meetings, tavus_function_definitions. CHECK constraints on status and kind enums. Reversible. No existing tables altered.


plan: Add 5 TypeORM entity classes. EnvironmentProfile in api/src/environment-profiles/. Meeting, MeetingDecision, MeetingPriority in api/src/meetings/. TavusFunctionDefinition in api/src/tavus/. TS-only status/kind union types match the migration CHECK constraints. Use simple-json for JSONB columns (also works in SQLite tests). Index decorators mirror the migration's CREATE INDEX statements.


summary: Five entity classes. Each carries org_id for the org-scoped four (TavusFunctionDefinition is global with no org_id). Indexes via @Index decorators mirror the migration. Status/kind columns typed as TS unions, validated server-side. simple-json for JSONB columns (TypeORM normalizes this to JSONB in Postgres and TEXT/JSON in SQLite tests). Discovered during PM-0001 context read that the existing Action entity types (ActorType, ActionType) are TS-only string unions over a VARCHAR column — so PM-0044's write to actions with actorType='reporting_meeting' will use 'as ActorType' cast at the new code's write-site without modifying common/entities.ts. Noted for PM-0044.


plan: Add additive env-var listing of the four new org-scoped persona-manager tables to nightly-isolation.yml. The workflow runs an agent eval that doesn't currently consume a table list, but PM-0063 will add a NestJS-side persona-manager-isolation.spec.ts that reads HUMANWORK_ISOLATION_PERSONA_TABLES. Single-line addition; no other workflow job touched.


summary: Added HUMANWORK_ISOLATION_PERSONA_TABLES env var listing environment_profiles, meetings, meeting_decisions, meeting_priorities. Single additive insertion under the existing env block; no other lines or jobs touched. PM-0063 will consume this env var when implementing the actual cross-tenant isolation spec.


plan: Phase 1 Tavus integration layer: TavusClient (REST methods + 5min TTL replica cache + normalized errors), WebhookVerifierService (HMAC-SHA256 mirror of channels pattern, no import), WebhookRouterService (Redis-backed idempotency with in-memory fallback + function-call dispatch table), TavusController (GET /tavus/replicas + two webhook endpoints), TavusModule registration in app.module.ts. Unit tests for all three services.


summary: Phase 1 complete. TavusClient uses native fetch (matches request-finance.provider.ts pattern), normalises upstream errors to TavusClientError so the UI never sees raw Tavus payloads, caches replicas for 5min. WebhookVerifierService mirrors the channels HMAC pattern (createHmac+timingSafeEqual) without importing the channels code, accepts bare hex or 'sha256=' prefixed signatures, hard-fails at boot in deployed envs if TAVUS_WEBHOOK_SECRET unset. WebhookRouterService backs idempotency with Redis SET NX EX (24h TTL); falls back to a process-local Map if Redis is unreachable. TavusController exposes GET /tavus/replicas behind PlatformRoles(superadmin, account_manager, client_admin, org_admin) and two HMAC-verified, idempotent webhook endpoints; reads rawBody from req.rawBody when available with a dev-only stringify fallback. Single-line TavusModule import added to app.module.ts (permitted touch). 22 unit tests, all passing. Cross-tenant isolation for tavus_function_definitions is N/A (global table, no org_id; SuperAdmin-only at controller layer).


plan: Phase 2 function-call library: (PM-0020) TavusFunctionDefinitionsService CRUD with versioning via publishNewVersion, controller gated to PlatformRoles(superadmin) for mutations, AMs can read. AuditService log on every state change. (PM-0021) Seed migration 1746000000011 inserts the 12 v1 function definitions with full JSON Schema parameters. ON CONFLICT DO NOTHING for idempotency. (PM-0022) Frontend page /ops/personas/function-calls with list, filter by kind, edit description + JSON Schema. Additive functions appended to frontend/src/lib/api.ts.


plan: Phase 3 Environment Profile + onboarding flow. EnvironmentProfilesService for CRUD with org-scope assertion and audit. OnboardingHandlers register 6 Tavus function-call handlers with the existing WebhookRouterService at module init. SoulMdGeneratorService renders approved profile to orgs/<slug>/SOUL.proposed.md (never touches SOUL.md). MeetingsService.startOnboarding is idempotent via the partial unique index installed in PM-0003; creates EnvironmentProfile, Meeting row, Tavus persona, Tavus conversation. Onboarding prompt template at agent/prompts/onboarding_meeting.txt — read by NestJS at start time (no agent loader change). Frontend page /client/admin/specialists/[id]/onboarding/ with start, edit, approve flows. Additive frontend API functions.


summary: Phase 5 SuperAdmin surfaces. Replicas page is a read-only Tavus replica catalogue with copy-id-to-clipboard interaction. Templates page documents where the prompt templates live (agent/prompts/*.txt) and the placeholder contract. Audit filter additions are not needed — every persona-manager state-changing operation already routes through AuditService.log, so the existing audit log viewer surfaces them by resourceType filter.


summary: Phase 6 hardening complete. Playwright E2Es for onboarding (start → simulate function calls → review → approve), reporting (schedule → start → log decision/priorities/escalation → verify audit), and the 409 replica_not_set negative path. All gated by PERSONA_MANAGER_E2E=1 env so CI doesn't try to run them without a stack. Cross-tenant isolation spec runs via mocked repos and asserts ForbiddenException on cross-org appendToBucket / setBucket / approve / patchProfileData / markPendingApproval, plus that createOrGet keeps orgs separate and getById doesn't leak orgId presence. PersonaCleanupService.sweep() archives Tavus personas after PERSONA_CLEANUP_AFTER_DAYS (default 14); PersonaCleanupProcessor wires a 24h BullMQ repeatable job (no-op without REDIS_URL). Rate limits inherited via existing global OrgThrottlerGuard. Docs at docs/persona-manager/{README, api, function-calls}.md. Total persona-manager tests: 51 passing across 7 spec files.


summary: Supervisor cycle-2 doc fixes. Corrected the RLS migration reference list (9, 12 MemoryRLS, 13, 17, 27, 33) across spec / ADR-0001 / migration header comment — migration 20 was a typo (it's OnboardingOtps, no RLS). HMAC mirror reference now points at the email-handler per-request block at channels.controller.ts:445-465, in addition to the boot-guard block at :47-62. Dropped the forward reference to ADR-0002 in spec §6.3 — rationale lives inline. PM-0069/PM-0072 schema-source pointers acknowledged; schemas retained as Builder v1 derivations; an alignment ADR can be queued if drift becomes load-bearing.


summary: In-memory webhook dedupe map is now bounded at MEM_DEDUPE_MAX_SIZE=10_000 with insertion-order eviction down to 90% on overflow. Map.keys() returns insertion order so deletion-head gives LRU-ish behaviour at O(1). Existing TTL eviction is preserved. Existing webhook-router tests still pass (13 across router + isolation specs).


summary: Reporting escalation no-conversation-id silent-bypass eliminated. Schema for raise_escalation now requires conversation_id (migration 1746000000012); handler rejects calls without it server-side, audits meeting.escalation_rejected with attempted_summary + attempted_risk_level so the operator can act, and emits a Socket.io meeting_function_call event with rejected:true so the UI sees the failure. Defensive try/catch around ExpertQueueService.createQueueItem is retained as a degraded-mode fallback (and now audits meeting.escalation_queue_failed separately).


reason: Service-layer TypeORM integration tests require either a Postgres connection or a working better-sqlite3 native binary. The Builder env's npm ci fails compiling better-sqlite3 (node-gyp errors on node 25); Postgres isn't available locally. Mock-repo coverage is in place; full integration coverage requires CI to run.

what_would_unblock: Either (a) push the branch and rely on the existing CI Postgres service container to exercise integration, or (b) document a local-dev npm rebuild path for better-sqlite3 against the target node version.


decision: Close PM-0074 under Supervisor option-2 (accept the integration-test gap; rely on CI Postgres service container as the integration gate on first push).

rationale: better-sqlite3 native binary fails to compile in this env (node 25.9.0 + node-gyp 8.4.1 incompatibility); Postgres is unavailable locally. CI's existing test-api job runs against a real Postgres service container per CLAUDE.md and ci.yml — the same harness that already covers the existing 35 spec files. The Builder is not authorized to push to remote unsolicited (system rule), so option-1 (push the branch) cannot be taken autonomously. User directed 'continue until done', interpreted as authorisation for option-2 (explicit accepted-risk close).


summary: PM-0074 moved Blocked → Done. ADR-0002 documents the five specific risks (simple-json/JSONB divergence, DB-layer RLS enforcement, FK cascades, partial unique index race, audit visibility) and the mitigation per risk. Nightly-isolation.yml already lists HUMANWORK_ISOLATION_PERSONA_TABLES for DB-layer RLS coverage on first cron run after merge.


summary: PM-0000..PM-0075 all in Done. Active and Blocked sections of TODO_PERSONA.md both empty. 51 persona-manager unit tests pass across 7 spec files. 8 commits on dev (697daee → 581193e). Zero bytes modified in existing hwork-app code; only the four documented additive touch points received changes. ADR-0001 documents the integration approach; ADR-0002 documents the deferred integration-test-coverage decision. Awaiting Supervisor's supervisor_complete event in SUPERVISOR_LOG.jsonl OR an explicit user signal to push the branch to remote for CI integration.


summary: PM-0000..PM-0075 complete. TODO_PERSONA.md Active is empty. PM-0074 remains in Blocked awaiting CI Postgres integration. Six commits on dev: 697daee (PM-0000..0002 spec+context+ADR), 6afad97 (PM-0003..0005 schema+entities+isolation env), 8d3cc5d (PM-0010..0016 Tavus layer), 643922d (PM-0020..0022 function-call library), f1b03f5 (PM-0030..0036 onboarding flow), e1b6756 (PM-0040..0047 reporting flow + brief), b758536 (PM-0050..0075 SuperAdmin surfaces + hardening + Supervisor amendments). 51 persona-manager unit tests passing across 7 spec files. No existing code modified except the four documented additive touch points (app.module.ts, lib/api.ts, nightly-isolation.yml, common/entities.ts — last one only via runtime cast at the new code's write-site, not file edit). Awaiting supervisor_complete in SUPERVISOR_LOG.jsonl per Builder operating model.


summary: Phase 4 complete. BriefAssemblerService runs read-only against Conversation, Message, ExpertQueueItem, Action, Correction, Meeting, MeetingDecision repos; assembles 9 buckets; enforces a 32KB budget by halving every bucket > 1 per iteration up to 12 iters; marks _truncated when trimming occurs. PersonaGeneratorService renders agent/prompts/reporting_meeting.txt with {{ BRIEF_JSON }} substituted and calls TavusClient.createPersona + createConversation. MeetingsService.startReporting is idempotent (returns existing URL on second call), audits, and emits the meeting_started Socket.io event on the existing /notifications namespace; failure → status=failed. ReportingHandlers registers 6 reporting function-call handlers + a synthetic __transcript_ready__ handler with the existing WebhookRouterService. log_manager_decision writes BOTH meeting_decisions AND the existing actions table (actorType='reporting_meeting' as ActorType cast — the DB column is VARCHAR; no common/entities.ts modification needed); FK linkage via meeting_decisions.action_id. raise_escalation calls ExpertQueueService.createQueueItem when conversation_id is supplied; without one, records as a high-priority follow-up on the environment profile (queue requires conversation_id; we don't bypass). update_priorities writes one row per priority. Socket.io meeting_function_call event emitted from every handler. Frontend list + detail pages added; additive startReportingMeeting added to api.ts. 44 unit tests passing total.


summary: Phase 3 complete. Onboarding handlers register with the existing WebhookRouterService at OnModuleInit; no router code modified. Service writes use AuditService.log on every state-changing operation. SoulMdGeneratorService writes only orgs/<slug>/SOUL.proposed.md, never SOUL.md, with a deterministic render and safe-slug regex. MeetingsService.startOnboarding is idempotent (returns existing active onboarding URL on repeat calls; uses concurrent-write recovery via the partial unique index); fails cleanly to status=failed if Tavus creation throws so the next start can run. Onboarding prompt is plain-text with {agent_name} and {agent_context_section} placeholders; NestJS renders it before passing to tavusClient.createPersona — agent-side prompt loader is unchanged. Controller surfaces 409 with code 'replica_not_set' when TAVUS_DEFAULT_REPLICA_ID is unset (PM-0062 will assert this UI-side). SuperAdmin/AM org-scope resolution for elevated reads is a documented stub that returns 403 until PM-0044; client_admin/org_member paths are fully wired.


summary: Phase 2 complete. TavusFunctionDefinitionsService implements CRUD + publishNewVersion (clones latest, bumps version, keeps prior row) + deactivate. Schema-shape guard rejects non-object schemas. Every state change audits via existing AuditService. Controller gated: AMs can read, only superadmin can mutate or deactivate. Seed migration 1746000000011 inserts 12 v1 function definitions (6 onboarding + 6 reporting) each with a complete JSON Schema; ON CONFLICT (name, version) DO NOTHING for idempotency. Frontend page /ops/personas/function-calls renders the list with kind filter and inline JSON-schema editor for superadmin. Additive frontend API functions: listTavusFunctionDefinitions, getTavusFunctionDefinition, createTavusFunctionDefinition, updateTavusFunctionDefinition, publishTavusFunctionDefinitionVersion, deactivateTavusFunctionDefinition, listTavusReplicas. Mock-repo unit tests (11) instead of TypeORM integration to avoid better-sqlite3 native-binary dependency; service is repository-pattern thin so the mock surface is small. All passing.


summary: ADR-0001 documents the chosen integration approach (Option B: additive re-implementation in NestJS, not lift-and-shift of the POC). Captures the three options considered, the decision rationale, positive/negative consequences, and the exhaustive list of permitted touches to existing files (frontend/src/lib/api.ts additive only, api/src/app.module.ts single-line imports, .github/workflows/nightly-isolation.yml additive entity entries). Future cycles cite this ADR when triaging requires_existing_code_change events.


change_description: Added 5 persona-manager entity imports and 5 entries to the global entities[] array (lines 138-142). The original integration contract said app.module.ts changes were limited to single-line module imports, but this extra registration is required because TypeORM Repository<T> injection inside the new modules fails with EntityMetadataNotFoundError if the entity is not in the DataSource entities[] array. forFeature() alone is not sufficient; the connection must know about the entity for metadata to load. autoLoadEntities:true would be a one-line alternative but is conventionally avoided in this codebase per the explicit entities[] enumeration.

why_needed: Without this change, every new persona-manager controller returns 500 with EntityMetadataNotFoundError on first call. Smoke test of /ops/personas/function-calls reproduces this immediately. The forbidden touch was discovered at test time, not in spec review, because the issue is invisible to unit tests that mock the Repository.

alternatives_considered: (a) autoLoadEntities:true — one-line addition to TypeOrmModule.forRootAsync; rejected because the codebase convention is explicit entities[] enumeration so future readers find new entities by scanning that array. (b) Move entity registration into a sub-DataSource per module — large architectural change, breaks the existing single-connection assumption. (c) Run only migrations (set migrationsRun:true) — incompatible with dev workflow where synchronize is the source of schema.