ADR-0001 · Persona Manager integration approach
Status: Accepted · Date: 2026-05-13 · Deciders: Builder agent (autonomous) · Supersedes: none
Context
A standalone Persona Manager POC at hwork-persona-manager/ is build-complete.
It is its own FastAPI + Next.js stack with its own Postgres, its own Celery
queue, its own audit log, and its own hwork_client outbound to the main
Human.work application.
The integration task is to land its functionality inside the existing
Human.work monorepo (api/ NestJS, agent/ FastAPI, frontend/ Next.js)
without disrupting a production-grade codebase that is one operational task
away from MVP launch (45/46 P0 items complete per
MVP_LAUNCH_CHECKLIST.md).
The Builder operating prompt (prompts/persona-builder.md) sets a hard
constraint: do not modify existing code unless a task explicitly tells
you to.
Three high-level approaches were considered.
Options
A — "Lift and shift": import the POC repo as-is
Copy hwork-persona-manager/apps/api into the monorepo as a third backend
app alongside api/ and agent/. Run it on its own port and DB connection.
Bridge to hwork-app over HTTP using the POC's existing hwork_client.
- Pros: preserves the POC verbatim. Zero re-implementation effort.
- Cons: duplicates auth, multi-tenancy, audit, the Postgres connection, CI orchestration, deployment surface, secret management. Two systems drift over time. Cross-tenant isolation now spans two services and two DBs — testing it nightly becomes a meaningfully harder problem. Violates the spirit of "single source of truth for org data."
B — "Additive re-implementation": rebuild inside the NestJS app
Build new NestJS modules (api/src/environment-profiles/,
api/src/meetings/, api/src/tavus/) and new Next.js routes that read from
the existing entities, write to five new tables, and reuse all existing
cross-cutting infrastructure (AuditService, OrgRlsInterceptor,
NotificationsGateway, JwtAuthGuard, PlatformRolesGuard,
OrgRolesGuard, ExpertQueueService).
- Pros: single Postgres connection, single auth model, single audit trail, single multi-tenancy mechanism. The integration touch points are countable and reviewable. Tests run in the existing harness. Nightly cross-tenant isolation already covers the new tables when added to its list (PM-0005). No drift between two parallel systems.
- Cons: re-implements ~70% of what the POC already wrote in Python +
SQLAlchemy. The POC's Celery workflow becomes BullMQ. The POC's separate
Postgres becomes a schema namespace inside the main one. Some POC
abstractions don't translate (e.g.
work_log_referencecollapses to live service-layer reads).
C — "Hybrid extraction": vendor select POC pieces, ignore the rest
Pull only the Tavus client, prompt templates, and function-call definitions
from the POC; ignore everything else and rebuild the rest. Use the POC's
hwork_client reversed — call from POC outward into hwork-app — converted
to a NestJS module.
- Pros: salvages the genuinely reusable POC outputs (Tavus contract understanding, function-call schemas, prompt structure).
- Cons: ambiguous boundary. Halfway between A and B without the benefits of either. Hard to draw the line during implementation.
Decision
Option B. Additive re-implementation inside the NestJS app.
The five new tables (environment_profiles, meetings, meeting_decisions,
meeting_priorities, tavus_function_definitions) stand alone and reference
existing entities by FK. New code lives in new directories. Cross-cutting
infrastructure is reused, not duplicated. Agent service gets new prompt
files but no code changes (the existing load_prompt(role) already routes
on filename).
The POC remains in hwork-persona-manager/ as reference material for entity
mapping, function-call schemas, and Tavus contract details, but it is not
imported, deployed, or run.
Consequences
Positive
- One auth model, one audit log, one DB connection, one multi-tenancy mechanism, one notification gateway.
- The integration touch points are explicit and small: additive functions in
frontend/src/lib/api.ts, single-line module imports inapi/src/app.module.ts, additive entity entries in.github/workflows/nightly-isolation.yml. Anything else in an existing file is a violation requiring arequires_existing_code_changeevent. - Reporting decisions ride the existing
Actionentity as a designed extension point (actorType=reporting_meeting). The existing audit log viewer surfaces them for free. - Reporting escalations go through
ExpertQueueService.create(...). The expert workspace queue surface is reused unchanged. - The brief assembler reads live from
Conversation/Message/Action/ExpertQueueItem/Correctionvia service-layer queries — no parallel work-log store, no sync job. - Single Postgres + single
OrgRlsInterceptorcovers tenancy. Each new table gets an RLS policy in its migration matching migrations 9, 12, 17, 20, 27, 33.
Negative / accepted
- Re-implementation effort. The POC's Python services become TypeScript services. The mitigation is that the POC's domain model carries over cleanly to TypeORM entities — most of the translation is mechanical.
- The POC's Celery + Redis async pattern is replaced by BullMQ (already in
package.jsonand minimally used). Only the persona archival job (PM-0066) actually needs background scheduling; the rest of the workflow is request-response. - Versioning of role templates (POC's
role_template_versionentity) is deferred. See ADR-0003 if/when an actual need arises.
Neutral
- The
agent/prompts/directory becomes a shared prompt registry read by both the agent service (load_prompt(role)) and the NestJSpersona-generator.service.ts(file read + parameter substitution before passing totavusClient.createPersona(prompt)). This is fine because the agent's loader keys onroleand the NestJS reader simply renders the same template into a different consumer. No code change to the loader.
Permitted touches to existing files
These are sanctioned by the Builder prompt and listed exhaustively here so future cycles can reference them without re-deriving:
| File | Allowed change | Why |
|---|---|---|
frontend/src/lib/api.ts | Append new exported functions only. No signature changes, no reordering, no deletion. | Single API client convention in the frontend. |
api/src/app.module.ts | Add imports: [..., NewModule] single-line entries. | NestJS module registration. |
.github/workflows/nightly-isolation.yml | Append new entity names to the test list. | Cross-tenant isolation is existential; new tables must be covered. |
Every other existing file is read-only. Any task that appears to require
otherwise must emit a requires_existing_code_change event.
Verification
- PM-0001 confirmed all referenced entities and patterns exist where the Builder prompt indicated.
- The HMAC mirror pattern (PM-0012) reproduces
channels.controller.ts:47-62without import. - The Action extension-point write (PM-0044) is exercised by the Phase 6 Playwright E2E test (PM-0061) which asserts the row appears in the existing audit log viewer.
References
prompts/persona-builder.md— Builder operating prompt (canonical)docs/specs/persona-manager-integration.md— system-level integration spechwork-persona-manager/docs/architecture.md— POC referenceapi/migrations/9, 12 (MemoryRLS), 13, 17, 27, 33 — RLS patterns (PM-0067 fix)api/src/channels/channels.controller.ts:23-62— HMAC patternapi/src/common/entities.ts—Actionextension-point entity