The design researches existing libraries before adding code. The repository keeps the runtime dependency-light for the current lab, while security-critical primitives use maintained libraries when the enterprise target requires them.
| Area | Library | Decision | Evidence |
|---|---|---|---|
| REST API | FastAPI | Use when the API moves beyond the current stdlib prototype. | FastAPI provides request validation with Pydantic models, standard status/response declarations, and OpenAPI/JSON Schema generation. Context7: /fastapi/fastapi. |
| Admin console | React-admin | Use for production CRUD/admin surfaces. | React-admin has Admin, Resource, dataProvider, authProvider, i18nProvider, dashboard, layout, and custom route hooks. Context7: /marmelab/react-admin. |
| i18n | i18next | Use for shared web translation runtime, especially outside React-admin defaults. | i18next supports resource bundles, fallbackLng, interpolation, language detection, and runtime changeLanguage. Context7: /i18next/i18next. |
| Persistence | SQLAlchemy 2.x | Use for Python domain persistence. | Official docs cover ORM mapped classes and sessions. |
| Migrations | Alembic | Use for schema migration lifecycle. | Alembic is the SQLAlchemy migration tool and supports autogenerated migrations from metadata. |
| Database | PostgreSQL | Default relational store. | PostgreSQL identifiers allow letters, digits, and underscores; the project standardizes on unquoted lower snake_case. |
| API contract | OpenAPI 3.1 | Contract format for API review and client generation. | OAS defines a language-agnostic HTTP API description for humans and machines. |
| TLS trust | certifi | Add Mozilla's public CA bundle to the platform trust store when no operator ca_bundle is configured. An explicit bundle replaces both; system-only trust is insufficient where Python does not expose a complete public-root store. |
ssl.create_default_context() preserves native enterprise roots, while load_verify_locations(certifi.where()) adds maintained public roots without disabling verification. |
| Rendered policy browser | MCP Python SDK | Keep as a pinned deployment-provided optional package for the existing Camoufox MCP transport; do not claim a repository policy-browser extra until this project owns that lock and publish contract. |
Reuses the protocol client and Streamable HTTP lifecycle instead of implementing a second transport; static policy analysis does not install it. |
| Structured-output validation | jsonschema |
Use the maintained validator for provider-returned JSON against caller-supplied JSON Schema; keep parsing and the single repair policy in the existing orchestrator. | Reusing validator_for, schema checks, and bounded validation avoids an incomplete custom JSON Schema implementation. Provider output and schemas remain untrusted and fail closed. |
| SSE usage capture | Python stdlib streaming parser already used by ModelClient._stream_send |
Reuse the existing line-delimited SSE parser and capture only provider-declared usage frames; do not add an SSE or provider SDK dependency. | OpenAI's Responses and Chat Completions references define terminal usage fields, while interrupted streams may omit the final usage frame. |
| Verbose/debug logging | Python stdlib logging (researched: structlog, loguru) |
Use stdlib logging exclusively -- logging.basicConfig(..., force=True) for one configuration entrypoint, a logging.Filter on the installed handler for redaction, %-style lazy formatting for cost-free DEBUG below its threshold. structlog/loguru add structured/prettier output this repo's existing print(json.dumps(...)) CLI-report convention and _LOGGER = logging.getLogger(__name__) precedent (3 modules) do not need yet. |
Python's own logging HOWTO documents basicConfig's one-shot-unless-force behavior, handler-level Filters, and that isEnabledFor gates expensive argument construction, not just formatting -- covering every requirement (level control, lazy evaluation, a redaction hook) with zero new dependency surface. |
| Distributed tracing | OpenTelemetry Python (already a runtime dependency since ADR 0122; recorded here for completeness -- this row was missing when that ADR shipped) | Keep as the request-correlation/span backend for cross-provider tracing (telemetry.py); it stays a separate system from stdlib logging (verbose/debug logging row above) -- two systems, not one, because OTel's span/attribute model and stdlib logging's line-oriented model solve different problems and merging them would require a third abstraction neither currently needs. |
OpenTelemetry's Python SDK and OTLP HTTP exporter are the maintained reference implementation for the vendor-neutral tracing API this repo's GenAI span conventions already target (see ADR 0122's References). |
| Provider-embedding claim ownership | Existing redis-py lock token plus one Valkey Lua transaction |
Propagate renewal loss, compare the live execution token, and atomically write terminal state with result/usage/error. A live worker retries claim acquisition until terminal state or deadline; provider-side exactly-once execution is not claimed. | Redis's official distributed-lock guidance requires ownership-safe release/extension and recommends fencing when correctness depends on exclusive work. Reused the existing registry and skipped a new coordination dependency, forced cancellation of synchronous provider I/O, and an unsupported provider-idempotency claim. |
| Provider-embedding token accounting | Existing PyO3 + tiktoken-rs extension, with configured pg_tiktoken first |
Load the packaged Rust extension in the production embedding path for the exact OpenAI-published cl100k embedding model IDs. Missing/failing native code and unknown tokenizers are explicitly unavailable; splitting, provider dispatch, usage, and cost fail closed instead of estimating. | OpenAI's public encoding table maps text-embedding-ada-002, text-embedding-3-small, and text-embedding-3-large to cl100k; PyO3 publishes the existing module in-package. Skipped tokenizer-name inference, a second tokenizer implementation, a provider SDK, and heuristic fallback. ADR 0006 now governs chat accounting separately. |
| Chat token accounting | Existing provider usage fields plus the packaged PyO3 + tiktoken-rs extension |
Treat valid provider usage as authoritative. Use Rust only for raw textual output from exact model IDs declared by ADR 0006; prompt framing, tools, multimodal input, unknown models, missing native code, and missing stream usage are explicitly unavailable. Enabled budgets fail closed and token-threshold routing remains synchronous when the required count is unavailable. | OpenAI's Chat Completions contract carries provider usage and notes streamed usage can be absent; OpenAI's public tiktoken model table separates exact mappings from unsafe prefix matching. Reused the existing extension and storage status seam. Skipped a provider SDK, prompt-serialization reimplementation, prefix/name inference, heuristic estimates, and fabricated zero-cost reporting. |
No new dependency is added until it carries real product weight:
- Current prototype: stdlib server, handwritten OpenAPI, static admin UI.
- First enterprise cut: FastAPI + React-admin + i18next + PostgreSQL + SQLAlchemy + Alembic.
- Do not add provider SDKs until raw OpenAI-compatible HTTP is insufficient.
Skipped: custom admin framework, custom i18n engine, custom migration engine.
For the KRW 2,000,000,000 commercial-readiness plan, keep Contextual Orchestrator as one repository and one deployable product. Do not split the orchestration core into a separate library, Git submodule, or package yet.
Reason:
- The buyer value is the integrated system: compatible API, admin evidence surface, workflow trace, access-list reports, analytics snapshot, sales readiness, and commercial readiness.
- A separate library would create release, versioning, and support overhead before there is an external SDK consumer or independent orchestration-core release cadence.
- A Git submodule would make due-diligence review harder because buyers need a single evidence packet, not a multi-repo dependency chain.
Extraction triggers:
- A second product or external customer needs the orchestration engine without the admin control plane.
- The orchestration core needs a separately versioned API and compatibility matrix.
- Security review requires a reusable, locked core package with independent provenance.
Until those triggers exist, Ponytail recommends strengthening the current single-repo product instead of splitting it.
Issue #568 needs a provider-neutral reasoning_effort_profile and an
equal-budget ablation against true parameters. Sampling temperature is not
reasoning effort.
| Area | Researched | Decision | Skipped |
|---|---|---|---|
| Profile object | Existing OrchestrationPolicy dataclass; OpenAI reasoning_effort enum; Anthropic thinking-token budget |
New stdlib module reasoning_effort_profile.py with a frozen dataclass, fail-closed parser, role catalog, and snapshot hash. No production-default change until the RMSE gate passes. |
New dependency, provider SDK, treating temperature as an effort proxy, a second policy factory. |
| Ablation | Fugu latency-vs-quality frontier; TRINITY role split; Conductor steps/access lists; Baker (2001) IRT true-θ RMSE | Deterministic offline θ̂ = (1−λ)θ with RMSE(θ̂, θ). Access-list scope and recursion depth change λ. Record quality, budget, estimated tokens used, and measurement_status=estimated. Persist the same snapshot on run / stream_route / batch_route. |
Live NVIDIA NIM calls in this slice (issue #86 evidence plane); changing OrchestrationPolicy defaults. |
| Doctoring | Sakana Fugu (2026); Xu et al. (2025) TRINITY arXiv:2512.04695; Nielsen et al. (2025) Conductor arXiv:2512.04388 | APA 7th citations in docs/architecture.md and docs/papers/README.md. PDFs are not vendored when redistribution is unclear. |
Training a learned coordinator. |
Buyer next action: call default_role_effort_catalog() / run_equal_budget_ablation()
and keep route/conduct defaults unchanged until production_default_change_allowed
returns true.
Issue #927 needs real per-model output-ceiling and context-window metadata from discovery. Provider docs were re-checked against live public sources on 2026-08-31 rather than recalled from memory because these schemas drift.
| Area | Researched | Decision | Skipped |
|---|---|---|---|
| OpenRouter metadata | Live https://openrouter.ai/openapi.yaml; current Model.context_length; current TopProviderInfo.max_completion_tokens |
Parse context_window from context_length and max_output_tokens from top_provider.max_completion_tokens. Keep them separate because OpenRouter documents them as different limits. |
Inferring output ceiling from context_length alone. |
| Models.dev-enriched providers | Live https://models.dev/api.json; current limit.context; current limit.output |
Merge models.dev limit.context into context_window and limit.output into max_output_tokens for providers already enriched from Models.dev (openai, opencode_zen, nvidia_nim, nvidia_nim_sub). |
Guessing limits from modality, family, or price metadata. |
| Configured gateway model info | Existing LiteLLM-style model/info merge in model_discovery.py |
Accept only explicit completion-ceiling fields (max_output_tokens / max_completion_tokens) and explicit context-window fields (context_window / context_length) when every deployment for one logical model agrees. |
Treating ambiguous max_tokens or max_input_tokens as an output ceiling. |
| Runtime enforcement | Existing stdlib ModelClient request assembly |
Clamp only provider-bound outgoing token-budget fields when an agent carries a known max_output_tokens; otherwise preserve caller and client defaults. |
New provider SDKs, tokenizer packages, or substituting context_window when the output ceiling is unknown. |
Every new subsystem design must update this file before implementation starts. The entry must name the existing libraries researched, the selected library or stdlib alternative, and the custom code that was deliberately skipped.
| Area | Library/pattern | Decision | Evidence |
|---|---|---|---|
| Field encryption | cryptography.hazmat.primitives.ciphers.aead.AESGCM |
Use the maintained AEAD primitive already available in the Python ecosystem; resolve the 256-bit key from the existing KV credential registry. Generated key bytes use explicit base64:/hex: encodings; marked passphrases use stdlib scrypt. Versioned ciphertext binds event context, key name, and field label as a canonical JSON AEAD associated-data array. |
OWASP Cryptographic Storage Cheat Sheet recommends authenticated encryption such as GCM; RFC 5116 defines the AEAD interface; Percival and Josefsson (2016), RFC 7914, specifies the memory-hard scrypt derivation function. |
| Key management | Existing credentials.get_credential |
Reuse the repository's KV seam; no runtime environment lookup and no second secret store. | NIST SP 800-57 Part 1 Rev. 5 covers key protection, inventory, access control, and rotation. |
| Purpose control | Existing bearer scopes plus fixed route purposes | Map authenticated inference and admin roles to explicit message_delivery, operator_read, and audit_replay purposes; audit every decision. |
Wolf, Pallas, and Tai (2021) describe purpose limitation for data-in-transit and access decisions in event-driven systems (arXiv:2110.15150). |
The implementation deliberately skips custom cryptography, automatic PII detectors, blanket masking, and a new policy framework. Callers explicitly declare the top-level event fields that contain PII; undeclared fields retain the existing behavior, while marked fields fail closed when the KV key is missing or invalid.
| Question | Evidence reread | Decision | Deliberately skipped |
|---|---|---|---|
| What may an observation claim? | Council of Europe CEFR Companion Volume and linking manual | Preserve criterion-level evidence and transparent linking inputs; do not emit a CEFR level or placement decision. | A local CEFR scale or standard-setting algorithm. |
| What makes an assessment result defensible? | AERA, APA, and NCME Standards for Educational and Psychological Testing | Keep task, rubric, criterion, anchor, evidence, rater, prompt, workflow, parse, verifier, and replay provenance explicit. | Calling provider success or a model label validity evidence. |
| How should provider calls be governed? | Existing TaskOrchestrator, KV credential registry, model discovery, and structured-output passthrough |
Reuse the existing gateway; require exact external contract compatibility and fail closed on missing capability or provider failure. | Direct provider SDK calls or a second credential path. |
| What proves a discovered configured-gateway chat row is usable? | The existing provider-error boundary distinguishes catalog metadata from runtime success. | Require one bounded synthetic structured-output probe before activation and share missing-model exclusions across the full virtual request. | Treating list membership as readiness or retrying the same missing model in later workflow roles. |
Official references:
- Council of Europe. (2020). Common European framework of reference for languages: Learning, teaching, assessment—Companion volume. https://www.coe.int/en/web/common-european-framework-reference-languages/cefr-companion-volume-and-its-language-versions
- Council of Europe. (2009). Relating language examinations to the Common European Framework of Reference for Languages: Learning, teaching, assessment—A manual. https://www.coe.int/en/web/common-european-framework-reference-languages/relating-examinations-to-the-cefr
- American Educational Research Association, American Psychological Association, & National Council on Measurement in Education. (2014). Standards for educational and psychological testing. https://www.aera.net/Publications/Books/Standards-for----Educational-Psychological-Testing-2014-Edition
The cited standards are linked rather than vendored because redistribution permission for their PDFs is not assumed.
ADR 0123 needs a grounded web-search tool. Metasearch and browser-automation options were checked live against their current repositories rather than recalled from training data, because license and maintenance status drift.
| Area | Researched | Decision | Skipped |
|---|---|---|---|
| Metasearch engine | SearXNG (license: AGPL-3.0, confirmed live); its documented /search?format=json HTTP API |
Call a self-hosted SearXNG instance's JSON API as a plain HTTP dependency (no SearXNG code vendored — same sidecar-service boundary this repo already uses for Wardnet/Camoufox). AGPL-3.0 governs SearXNG's own source, not a caller that only sends it HTTP requests. | Vendoring SearXNG or its plugins into this repository. |
| Metasearch alternative #1 | Whoogle (license: MIT) | Rejected. The repository is archived (2026-08-14): Google closed the last scraping workaround Whoogle depended on in 2024, and the maintainer states search is non-functional. A permissive license does not offset a dead upstream. | Implementing a client for a defunct search backend. |
| Metasearch alternative #2 | YaCy (license: GPL-2.0-or-later, confirmed live); built-in JSON/XML search API | Documented as the next self-hosted engine to support (own crawled P2P index, not dependent on scraping another engine's HTML — architecturally different from SearXNG's federation model, which is what "plural" engines is actually for). Not implemented this slice; add a second _ENGINE_HANDLERS-style entry in web_search.py when a real deployment exists to test against. |
Implementing against an engine with no deployment to verify. |
| Metasearch fallback (commercial) | Brave Search API (independent index, official JSON API, has a free tier) | Documented as the fallback if self-hosted engine coverage/quality proves insufficient later; not implemented — no product requirement to pay for search yet. | Any commercial search integration in this slice. |
| Browser automation (for a later, separate slice) | Camoufox (license: MPL-2.0, confirmed live) | Confirmed: MPL-2.0, Playwright-API-compatible Firefox fork, no official MCP server. This repository already consumes a third-party MCP wrapper (ghcr.io/redf0x1/camofox-mcp, pinned by digest) for one narrow use (privacy_policy_analysis.py's Wardnet-proxied policy rendering) — reuse that existing, reviewed integration for the web-search follow-up rather than adding a second Camoufox transport. |
Building a first-party Camoufox MCP server, or a second parallel browser-automation dependency. |
| Transport / SSRF boundary | Existing ModelClient._validate_provider / ModelClient._open_provider (already reused by privacy_policy_analysis.crawl_policy_document for Wardnet) |
Reuse the existing validated-HTTP primitive for the SearXNG call: HTTPS-only unless the host is an explicit loopback address, private/loopback/link-local/reserved destination IPs rejected, no vendored HTTP client. | A new HTTP client dependency, or a second hand-rolled SSRF check. |
Session-isolation note: ContextualWisdomLab/quarantine-sandbox-runtime's
develop branch has no HTTP/CLI entrypoint or container backend yet (real
work is an unmerged Draft PR stack #1→#6→#9→#10→#13, externally blocked on
ContextualWisdomLab/.github#1590, no LSM-capable CI runner). Camoufox
browsing already ships in this repository today gated behind Wardnet
(DNS-pinned egress proxy + authenticated CONNECT boundary, see
compose.camoufox-wardnet.yaml and the "Web-search boundary" /
"Wardnet policy-document boundary" sections of docs/kv-credentials.md), not
quarantine-sandbox-runtime. ADR 0123 records this as an open reconciliation
question rather than silently picking one.