D-A12-6 — Shared-code extraction debt¶
Context¶
PORTFOLIO_PLAN_V3.md §8 extracts shared packages (X2 zarreh-agentkit, X3
@zarreh/agent-ui, X5 zarreh-docs-theme) only after a second working
implementation exists, and never designs one from a single instance. A12 is
that second implementation — but A2 (trade_sureillance_agent) was built
first out of the plan's original sequence, so no package has been extracted
yet at all.
Decision¶
A12 copies and adapts proven patterns from A2 directly into its own source tree rather than waiting to extract packages first:
settings.py,observability.py— copied near-verbatim (env-prefix and project name changed).api/rate_limit.py,api/middleware.py— copied verbatim; both are already framework-level, not app-level.- The §9.3 repo layout and its import-linter contracts — copied and extended
with one new contract (
pipelinemust never importlangchain/langgraph, D-A12-1) that A2 has no equivalent of, since A2 has no ML pipeline half. - The MkDocs site config and CI/CD workflow shape — copied and adapted.
- The frontend
PrototypeBannerpattern — copied;RunConsole,TraceTimeline,EvidencePanel,CostMeterwill be copied in Phase 8 once there is a graph to render.
zarreh-mlkit (X4) has no prior instance at all — per §16 it is born in
src/cohort/pipeline/, and A8 (Reliability Copilot) becomes its second
consumer later, at which point extraction is warranted by the same rule.
Consequences¶
- Real duplication exists between A2 and A12 today (settings shape, middleware, rate limiting, docs config). This is deliberate debt, not an oversight.
- The debt is paid down once a third repo needs the same code — at that point X2/X3/X5 extract from three concrete instances instead of two, which is a stronger basis for the abstraction than extracting after two.
- Nothing about A12's own correctness depends on the debt being paid down; it is purely a portfolio-wide maintenance cost, tracked here rather than hidden.
Update (2026-08-27) — the backend half is paid down¶
A3 (clinical_care_navigator) became the third repo. zarreh-agentkit
v0.2.0 now carries settings, observability (incl. build_tracing_callbacks,
independently reimplemented identically in A2/A3/A12), api.middleware,
api.rate_limit, guardrails.budget, guardrails.projection, and an
evals runner shell. A12's settings.py, observability.py,
api/middleware.py, api/rate_limit.py, and run_executor.py's tracing
callback now wrap the kit instead of duplicating it; guardrails/phi_projection.py
wraps zarreh_agentkit.guardrails.projection.project_fields, keeping only
its own TOOL_FIELD_ALLOWLIST local. Verified: make lint/typecheck/imports/test
(120/120) and make validate (18/18) all green afterward.
Still local by design, not debt: src/cohort/pipeline/ (the ML half — still
the only instance of the proto-zarreh-mlkit/X4 shape; A8 is its second
consumer) and validation/ (the Track-B-style harness, a different shape
from the agentic evals runner). The docs/CI/frontend duplication this ADR
also named is unaffected by this update — MkDocs/CI extraction is X5's
concern, not X2's.
Update — zarreh-agentkit v0.3.0¶
A12 had kept a private build_tracing_callbacks because the v0.2.x version
relied on an ambient LANGSMITH_API_KEY (which .env loading never exports),
so uploads 401'd. v0.3.0 binds the tracer to a langsmith.Client carrying the
key; cohort.observability is now a pure re-export, and
tests/test_observability.py guards the library behaviour.