diff --git a/AGENTS.md b/AGENTS.md index 7ced99577..1d5dceaae 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -56,11 +56,18 @@ push or open a PR. - The reference implementation is xtrmLLMBatchPython's pgcrypto-encrypted Postgres credential registry (`get_credential(name)`); reuse that pattern (a DB-backed KV is fine) unless a dedicated KV is adopted. -- **Known deviation to migrate:** this repo currently resolves provider API - keys from env — `ModelClient` reads `os.environ.get(agent.api_key_env)` in - `contextual_orchestrator/orchestrator.py` (and `CONTEXTUAL_ORCHESTRATOR_*` - tokens in `__main__.py`). Move these to KV-backed reads; keep env only as the - bootstrap path that seeds the KV. +- **Known remaining bootstrap:** `--host` / `--port` stay process bind + addresses (the platform injects them). `--insecure-skip-tls-verify` and + `--allow-public-bind` stay explicit CLI flags. Provider API keys, gateway + Bearer authenticators (`gateway_auth_token` / `admin_auth_token` / + `inference_auth_token`), the provider-host allowlist, and serve sqlite / + Clearfolio / TLS CA paths are KV-backed (`get_credential`, + `resolve_server_auth_tokens`, `allowed_provider_hosts`, + `resolve_serve_runtime_paths`). Env values are bootstrap transport via + `seed_server_auth_from_environ` / `seed_serve_runtime_from_environ` / + `seed_provider_egress_from_environ` only. Do not reintroduce `os.getenv` + inside `ModelClient._validate_provider`, `ModelClient.chat`, + `serve_security_tokens`, or `serve_runtime_paths` resolution. ### This repo: the org LLM gateway @@ -68,9 +75,13 @@ push or open a PR. OpenAI-compatible front door consumed by **gyeot** and **scopeweave**. - **Direction:** grow it toward a **LiteLLM-class multi-provider gateway**. The org is open to a **Rust/Python hybrid** to cut overhead. -- Its `ModelClient` currently reads `os.environ.get(agent.api_key_env)` — this - is the KV-principle deviation above. Resolve the API key (including the org - `OPENAI_API_KEY`) from the **KV / credential registry**, not env. +- Provider API keys and gateway Bearer authenticators resolve through + `get_credential` (`gateway_auth_token`, `admin_auth_token`, + `inference_auth_token`). The provider-host allowlist resolves through + `allowed_provider_hosts()` on the **process-wide runtime ConfigStore** + (KV category `provider_egress`). That store is `InMemoryConfigStore` + unless bootstrap installed another backend with + `set_runtime_config_store()`. Env remains bootstrap transport only. - The **OpenCode review pipeline is separate** and stays on **GitHub Models** — do not change it. diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..6a8021d3f --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,95 @@ +# Changelog + +All notable changes to this project are documented in this file. + +The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Changed + +- Trusted orchestration traces no longer irreversibly mask email addresses. + Credential shapes (`api_key=`, `Bearer …`) stay `[REDACTED]`. Access control + (opt-in `include_orchestration_trace`) plus audit is the PII control, not + destruction of the identifier an operator needs to close an invoice or HR + ticket. Next action: request the trace only from a trusted caller; do not + expect emails in that trace to become `[REDACTED]`. + +### Added + +- Chat ``image_url`` parts now fail closed on HTML, ``javascript:``, SVG, and + truncated raster data URIs, and persist a 3NF ``message_image_unit`` with + the original ``part_index`` beside neighboring invoice text. Buyer next + action: send a complete PNG/JPEG data URI or ``https://…/receipt.png`` + next to the invoice line, then call ``list_message_image_units`` after + restart to reopen the figure that sat at that slot. + +### Fixed + +- Serve sqlite, Clearfolio, and provider TLS paths + (`serve_runtime.state_database_path`, `agents_database_path`, + `clearfolio_base_url`, `provider_ca_bundle`) resolve from the runtime + KV. `--state-db` / `--agents-db` / `--clearfolio-url` / + `--provider-ca-bundle` still win. The matching + `CONTEXTUAL_ORCHESTRATOR_*` env vars are copied into those KV keys once + at process start (`seed_serve_runtime_from_environ`). Changing the env + var on a running process no longer retargets persistence, the document + viewer, or provider TLS. Buyer next action: pass the CLI flags (or + start once with the env vars so bootstrap can copy them), then open the + KV sqlite path or Clearfolio URL. +- Gateway Bearer authenticators (`gateway_auth_token`, `admin_auth_token`, + `inference_auth_token`) resolve from the credential KV. `--auth-token` + and the split pair still win. `CONTEXTUAL_ORCHESTRATOR_TOKEN` / + `_ADMIN_TOKEN` / `_INFERENCE_TOKEN` are copied into those KV names once + at process start (`seed_server_auth_from_environ`). Changing the env + var on a running process no longer changes who can call the API. + Buyer next action: pass `--auth-token` (or start once with the env var + so bootstrap can copy it), then send that Bearer value. +- Provider host allowlisting (`provider_egress.allowed_provider_hosts`) is + read from the **process-wide runtime ConfigStore** at request time, not from + `os.getenv` and not from a separately constructed Postgres `com_config` + unless that store was installed with `set_runtime_config_store()` at + bootstrap. `CONTEXTUAL_ORCHESTRATOR_ALLOWED_PROVIDER_HOSTS` is copied into + that KV key once at process start (`seed_provider_egress_from_environ`). + Changing the env var on a running process no longer changes egress policy. + Buyer next action: call `set_runtime_config("provider_egress", + "allowed_provider_hosts", "api.example.com")` (or start the process with + the env var set so bootstrap can copy it). Do not write the key only into + a new `get_config_store(postgres_dsn=...)` instance and expect egress to + honor it. +- Treat official-SDK JSON `null` on optional `tools[].function.description`, + `parameters`, and `strict` as omit-real: the keys are popped before + `proxy_completion` so upstream providers see an omitted field, not a null + schema. Non-null wrong types still fail closed with named `invalid_tools`. + Next action: send those fields only when you have a real string, JSON Schema + object, or boolean; SDK defaults of `null` are safe. +- Fail closed on tools passthrough for `seed`, `stop`, `n>1`, `logprobs`, + `logit_bias`, and out-of-range penalties — the same named errors as the + orchestration path. Next action: omit those knobs on tool-calling requests. +- Apply the request `temperature` on streamed route completions instead of + silently using `0.2`. Next action: send the temperature you want; streaming + no longer changes the sampling policy. + +### References + +- McCallister, E., Grance, T., & Scarfone, K. (2010). *Guide to protecting + the confidentiality of personally identifiable information (PII)* (NIST + Special Publication 800-122). National Institute of Standards and + Technology. https://doi.org/10.6028/NIST.SP.800-122 +- Joint Task Force. (2020). *Security and privacy controls for information + systems and organizations* (NIST Special Publication 800-53 Rev. 5). + National Institute of Standards and Technology. + https://doi.org/10.6028/NIST.SP.800-53r5 +- Grassi, P. A., Garcia, M. E., & Fenton, J. L. (2017). *Digital identity + guidelines: Authentication and lifecycle management* (NIST Special + Publication 800-63B). National Institute of Standards and Technology. + https://doi.org/10.6028/NIST.SP.800-63b +- International Organization for Standardization. (2022). *Information + security, cybersecurity and privacy protection — Information security + controls* (ISO/IEC 27001:2022). https://www.iso.org/standard/27001 +- OpenAI. (2024). *Create chat completion*. OpenAI API reference. + https://platform.openai.com/docs/api-reference/chat/create +- Bray, T. (Ed.). (2017). *The JavaScript Object Notation (JSON) data + interchange format* (RFC 8259). Internet Engineering Task Force. + https://doi.org/10.17487/RFC8259 diff --git a/CLAUDE.md b/CLAUDE.md index f893b5f7b..e01444e8e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -85,10 +85,10 @@ A stdlib-Python lab implementing a single OpenAI-compatible API that routes, del ### Modules (`contextual_orchestrator/`) -- `orchestrator.py` — the domain heart: `ModelAgent`, `WorkflowStep`, `OrchestrationPolicy`, `ModelClient`, `TaskOrchestrator`, secret/PII redaction, budget enforcement, spend analytics, and the commercial-readiness report generators behind `/api/v1/*`. Domain code stays here until a second implementation forces extraction (see `docs/code_conventions.md`). +- `orchestrator.py` — the domain heart: `ModelAgent`, `WorkflowStep`, `OrchestrationPolicy`, `ModelClient`, `TaskOrchestrator`, credential redaction (operational email kept on trusted traces), budget enforcement, spend analytics, and the commercial-readiness report generators behind `/api/v1/*`. Domain code stays here until a second implementation forces extraction (see `docs/code_conventions.md`). - `server.py` — HTTP delivery adapter and `SecurityConfig`; all request validation lives here. - `admin.py` — static HTML/CSS/JS for the `/admin` operator console (stays inline while the product is dependency-free). -- `credentials.py` / `kv_config.py` — the KV seam: `get_credential`/`register_credential` over pluggable backends (`InMemoryCredentialBackend` default; pgcrypto-encrypted `PostgresCredentialBackend`, selected via `CONTEXTUAL_ORCHESTRATOR_KV_BACKEND`). +- `credentials.py` / `kv_config.py` — the KV seam: `get_credential`/`register_credential` over pluggable backends (`InMemoryCredentialBackend` default; pgcrypto-encrypted `PostgresCredentialBackend`, selected via `CONTEXTUAL_ORCHESTRATOR_KV_BACKEND`). Request-time provider host allowlisting uses `allowed_provider_hosts()` (`provider_egress.allowed_provider_hosts`). Gateway Bearer authenticators use `gateway_auth_token` / `admin_auth_token` / `inference_auth_token` via `seed_server_auth_from_environ` + `resolve_server_auth_tokens`; env is bootstrap only. - `cost_ledger.py` / `cost_router.py` / `batch_routing.py` / `token_counting.py` — the cost-review + routing hub: prompt-safe usage ledger with seven attribution dimensions, `RoutingPolicy` (sync vs batch from request hints + KV thresholds), and the [pg-llm-batch](https://github.com/ContextualWisdomLab/pg-llm-batch) batch/embeddings backends (a local in-process backend keeps the standalone path working with no external service). - `api_contract.py` / `conventions.py` — API-shape and naming-rule enforcement helpers. - `__main__.py` — the single entry point: CLI completion, `--serve`, `--eval`, and the `register-credential` bootstrap subcommand. diff --git a/README.md b/README.md index 65f57dd4c..0b6dad248 100644 --- a/README.md +++ b/README.md @@ -36,11 +36,11 @@ curl -s http://127.0.0.1:8000/v1/chat/completions \ HTTP serving is hardened for local lab use: -- `/admin`, `/admin/state`, `/api/v1/*`, and `/v1/chat/completions` require a Bearer token. Use `--admin-token` and `--inference-token` to separate operator and runtime access, or `--auth-token` / `CONTEXTUAL_ORCHESTRATOR_TOKEN` for one local-development token. +- `/admin`, `/admin/state`, `/api/v1/*`, and `/v1/chat/completions` require a Bearer token. Use `--admin-token` and `--inference-token` to separate operator and runtime access, or `--auth-token` for one local-development token. `CONTEXTUAL_ORCHESTRATOR_TOKEN` (and the split admin/inference vars) are copied into the credential KV once at process start — send the seeded token; a later env edit does not change a live process. - Binding to `0.0.0.0` or `::` requires `--allow-public-bind`. - JSON request bodies, chat message roles, orchestration modes, body sizes, request rate, and concurrent run counts are validated before orchestration runs. -- Full orchestration traces are not returned by default. Set `include_orchestration_trace: true` per chat request or start with `--expose-trace-by-default` when the caller is trusted. -- State is in-memory by default. Pass `--state-db PATH` (or `CONTEXTUAL_ORCHESTRATOR_STATE_DB`) to persist workflow runs, evaluation runs, audit, and analytics to a stdlib sqlite file so they survive a restart; without it, behavior is unchanged. +- Full orchestration traces are not returned by default. Set `include_orchestration_trace: true` per chat request or start with `--expose-trace-by-default` when the caller is trusted. Trusted traces keep operational email so invoice and HR tickets stay workable; credential material is still `[REDACTED]`. +- State is in-memory by default. Pass `--state-db PATH` (or start once with `CONTEXTUAL_ORCHESTRATOR_STATE_DB` so bootstrap can copy it into `serve_runtime.state_database_path`) to persist workflow runs, evaluation runs, audit, and analytics to a stdlib sqlite file so they survive a restart; without it, behavior is unchanged. A later env edit does not retarget the file. - Response caching is off by default. Pass `--cache-ttl SECONDS` to serve identical requests (same messages + mode) from an in-memory TTL+LRU cache and skip the provider calls; `0` disables it. - `ModelClient.batch_chat(agent, {custom_id: messages})` runs many requests through the provider's Batch API (async, 24h completion window, typically ~50% cheaper) — suited to evaluation/benchmark workloads, not latency-sensitive chat. The mock path answers synchronously. @@ -60,7 +60,7 @@ Use real workers by replacing `mock://` agents with OpenAI-compatible endpoints. } ``` -The agent pool is manageable at runtime: `POST`/`PATCH`/`DELETE` on `/api/v1/agent_pools/default/worker_agents[/{id}]` add, govern, and remove model-group members. Pass `--agents-db PATH` (or `CONTEXTUAL_ORCHESTRATOR_AGENTS_DB`) to persist those changes to a stdlib sqlite file — stored changes overlay the seed agents file at startup, and removals write disabled tombstones so they survive restarts; without it the pool is in-memory as before. +The agent pool is manageable at runtime: `POST`/`PATCH`/`DELETE` on `/api/v1/agent_pools/default/worker_agents[/{id}]` add, govern, and remove model-group members. Pass `--agents-db PATH` (or start once with `CONTEXTUAL_ORCHESTRATOR_AGENTS_DB` so bootstrap can copy it into `serve_runtime.agents_database_path`) to persist those changes to a stdlib sqlite file — stored changes overlay the seed agents file at startup, and removals write disabled tombstones so they survive restarts; without it the pool is in-memory as before. A later env edit does not retarget the file. Seed the credential into the KV once at bootstrap: @@ -68,7 +68,7 @@ Seed the credential into the KV once at bootstrap: echo "$OPENAI_API_KEY" | python -m contextual_orchestrator register-credential --name OPENAI_API_KEY --value-stdin ``` -Non-mock providers must use `https://` URLs and a **resolvable KV credential** — a non-mock agent whose credential is missing raises `NotConfigured` rather than falling back to an environment variable. The runtime blocks loopback, private, link-local, multicast, and reserved provider addresses before sending a key. Set `CONTEXTUAL_ORCHESTRATOR_ALLOWED_PROVIDER_HOSTS` to a comma-separated host allowlist when only approved model gateways should be reachable. External calls use a timeout and default output token cap. +Non-mock providers must use `https://` URLs and a **resolvable KV credential** — a non-mock agent whose credential is missing raises `NotConfigured` rather than falling back to an environment variable. The runtime blocks loopback, private, link-local, multicast, and reserved provider addresses before sending a key. Seed `provider_egress.allowed_provider_hosts` on the **process-wide runtime ConfigStore** with `set_runtime_config` (or set `CONTEXTUAL_ORCHESTRATOR_ALLOWED_PROVIDER_HOSTS` at process start so bootstrap can copy it once) when only approved model gateways should be reachable. A write to a separately constructed Postgres `get_config_store()` is ignored unless that store was installed with `set_runtime_config_store()` at bootstrap. External calls use a timeout and default output token cap. > The legacy `api_key_env` field is still accepted for back-compat, but its value is now treated as the **credential name** in the KV, not as an environment variable to read. This supersedes the old `api_key_env` env pattern. @@ -76,12 +76,12 @@ Non-mock providers must use `https://` URLs and a **resolvable KV credential** One public interface: -- `/v1/chat/completions` accepts normal chat messages, and `"stream": true` returns an OpenAI-compatible `text/event-stream` of `chat.completion.chunk` deltas terminated by `data: [DONE]`. In **route** mode the worker's tokens are streamed live as they arrive from the provider (real token streaming); in **conduct** mode the multi-step answer is produced then framed as deltas (a workflow can't honestly token-stream a synthesizer that hasn't run yet). +- `/v1/chat/completions` accepts normal chat messages, and `"stream": true` returns an OpenAI-compatible `text/event-stream` of `chat.completion.chunk` deltas terminated by `data: [DONE]`. In **route** mode the worker's tokens are streamed live as they arrive from the provider (real token streaming); in **conduct** mode the multi-step answer is produced then framed as deltas (a workflow can't honestly token-stream a synthesizer that hasn't run yet). `/v1/completions`, `/v1/responses`, and `/v1/embeddings` are the matching compatibility surfaces. Send `stream_options.include_usage` / `include_obfuscation` as omitted, `null`, or `false`; `true` and unknown keys fail closed. Tool-calling bodies must send a non-empty `messages` array of objects — empty, omitted, or non-object entries fail closed before passthrough. Omit `routing.channel=batch` and `latency_tolerant=true` on tool-calling requests; this gateway has no batch job plane on passthrough. Omit `seed`, `stop`, `n>1`, and `logprobs` on tool-calling requests — those knobs are not applied on passthrough and fail closed instead of billing a silent drop. Streamed route requests apply the `temperature` you send; they do not silently fall back to `0.2`. Optional `tools[].function.description`, `parameters`, and `strict` sent as JSON `null` are popped before the provider hop so SDK defaults stay omit-equivalent. - `TaskOrchestrator.complete()` decides whether to route to one worker or run a short workflow. - `TaskOrchestrator.compare_to_baseline(prompts, mode)` (CLI `--eval PROMPT...`) measures the orchestration engine against a single-worker baseline — per-prompt and aggregate latency plus a structural coverage delta (contributing steps + verifier-pass presence). It is a measured tradeoff report, not a human-quality claim. - Responses include orchestration mode metadata, and trusted callers can request the full trace for audit. - `/admin` exposes an operator console for agent pool, policy, trace, and audit review. -- The admin console can use [Clearfolio](https://github.com/ContextualWisdomLab/clearfolio) as its document viewer: pass `--clearfolio-url URL` (or `CONTEXTUAL_ORCHESTRATOR_CLEARFOLIO_URL`) and the Integrations view gains a Document Viewer card (open viewer / deep-link `{url}/viewer/{docId}`). Default: disabled, console unchanged. +- The admin console can use [Clearfolio](https://github.com/ContextualWisdomLab/clearfolio) as its document viewer: pass `--clearfolio-url URL` (or start once with `CONTEXTUAL_ORCHESTRATOR_CLEARFOLIO_URL` so bootstrap can copy it into `serve_runtime.clearfolio_base_url`) and the Integrations view gains a Document Viewer card (open viewer / deep-link `{url}/viewer/{docId}`). Default: disabled, console unchanged. A later env edit does not retarget the viewer. - `/api/v1/spend_analytics/latest` exposes per-model token and cost spend aggregated from workflow runs. Output tokens use provider-reported `usage` when available and fall back to a ~4 chars/token estimate otherwise (each model row is labeled `usage_source: reported | mixed | estimated`); cost is computed only for models with an operator-supplied price (`TaskOrchestrator(price_per_million=...)`), otherwise reported as null with the model listed under `unpriced_models`. See [Observability & spend](#observability--spend). - `/api/v1/sales_readiness/latest` exposes a local enterprise-pilot readiness gate for API compatibility, operator evidence, workflow traces, evaluation replay, security posture, analytics truthfulness, locale parity, and provider egress safety. It is process-local evidence, not a production compliance certificate. - `/api/v1/commercial_readiness/latest` exposes a KRW 2,000,000,000 commercial due-diligence readiness gate. It is a buyer-review evidence snapshot, not a valuation guarantee or purchase commitment. @@ -256,6 +256,9 @@ python tests/test_admin_contract.py python tests/test_conventions.py python tests/test_api_contract.py python tests/test_security_hardening.py +python tests/test_provider_host_allowlist_kv.py +python tests/test_server_auth_kv.py +python tests/test_message_image_units.py python tests/test_repository_security_metadata.py python tests/test_product_planning_contract.py python tests/test_plugin_driven_artifacts.py diff --git a/contextual_orchestrator/__main__.py b/contextual_orchestrator/__main__.py index 5f68c3b74..1085ea96d 100644 --- a/contextual_orchestrator/__main__.py +++ b/contextual_orchestrator/__main__.py @@ -7,11 +7,48 @@ import os import sys -from .credentials import register_credential +from .credentials import register_credential, resolve_server_auth_tokens, seed_server_auth_from_environ +from .kv_config import ( + resolve_serve_runtime_paths, + seed_provider_egress_from_environ, + seed_serve_runtime_from_environ, +) from .orchestrator import ModelClient, TaskOrchestrator, load_agents from .server import SecurityConfig, serve +def serve_security_tokens(args: argparse.Namespace) -> tuple[str, str, str]: + """Seed env authenticators into the KV once, then resolve serve tokens. + + Explicit CLI flags win. Env is bootstrap transport only. Buyer next + action: pass ``--auth-token`` or start once with + ``CONTEXTUAL_ORCHESTRATOR_TOKEN`` so the KV can copy it. + """ + seed_server_auth_from_environ() + return resolve_server_auth_tokens( + auth_token=args.auth_token, + admin_token=args.admin_token, + inference_token=args.inference_token, + ) + + +def serve_runtime_paths(args: argparse.Namespace) -> tuple[str | None, str | None, str | None, str | None]: + """Seed env sqlite/Clearfolio/TLS paths into the KV once, then resolve them. + + Explicit CLI flags win. Env is bootstrap transport only. Buyer next + action: pass ``--state-db``, ``--agents-db``, ``--clearfolio-url``, and + ``--provider-ca-bundle``, or start once with the matching + ``CONTEXTUAL_ORCHESTRATOR_*`` variables so the KV can copy them. + """ + seed_serve_runtime_from_environ() + return resolve_serve_runtime_paths( + state_db=args.state_db, + agents_db=args.agents_db, + clearfolio_url=args.clearfolio_url, + provider_ca_bundle=args.provider_ca_bundle, + ) + + def _register_credential_command(argv: list[str]) -> None: """Bootstrap: read a deploy-time secret and store it in the KV credential registry. @@ -64,24 +101,48 @@ def main() -> None: parser = argparse.ArgumentParser(description="Route or conduct chat requests across model agents.") parser.add_argument("prompt", nargs="?", help="User prompt for CLI mode.") parser.add_argument("--agents", default="examples/agents.mock.json", help="Agent config JSON.") - parser.add_argument("--state-db", default=os.environ.get("CONTEXTUAL_ORCHESTRATOR_STATE_DB", "") or None, - help="Optional sqlite path to persist runs/audit/analytics across restarts (default: in-memory).") + parser.add_argument( + "--state-db", + default="", + help="Optional sqlite path to persist runs/audit/analytics. When omitted, bootstrap copies CONTEXTUAL_ORCHESTRATOR_STATE_DB into the serve_runtime KV once.", + ) parser.add_argument("--mode", choices=["auto", "route", "conduct"], default="auto") parser.add_argument("--serve", action="store_true", help="Run the chat completions HTTP server.") parser.add_argument("--host", default="127.0.0.1") parser.add_argument("--port", type=int, default=8000) - parser.add_argument("--auth-token", default=os.environ.get("CONTEXTUAL_ORCHESTRATOR_TOKEN", "")) - parser.add_argument("--admin-token", default=os.environ.get("CONTEXTUAL_ORCHESTRATOR_ADMIN_TOKEN", "")) - parser.add_argument("--inference-token", default=os.environ.get("CONTEXTUAL_ORCHESTRATOR_INFERENCE_TOKEN", "")) + parser.add_argument( + "--auth-token", + default="", + help="Gateway Bearer token. When omitted, bootstrap copies CONTEXTUAL_ORCHESTRATOR_TOKEN into the credential KV once.", + ) + parser.add_argument( + "--admin-token", + default="", + help="Admin Bearer token. When omitted, bootstrap copies CONTEXTUAL_ORCHESTRATOR_ADMIN_TOKEN into the credential KV once.", + ) + parser.add_argument( + "--inference-token", + default="", + help="Inference Bearer token. When omitted, bootstrap copies CONTEXTUAL_ORCHESTRATOR_INFERENCE_TOKEN into the credential KV once.", + ) parser.add_argument("--allow-public-bind", action="store_true") parser.add_argument("--insecure-disable-auth", action="store_true", help="Deprecated; API auth is always required.") parser.add_argument("--expose-trace-by-default", action="store_true") - parser.add_argument("--clearfolio-url", default=os.environ.get("CONTEXTUAL_ORCHESTRATOR_CLEARFOLIO_URL") or None, - help="Base URL of a Clearfolio deployment to use as the admin document viewer (default: disabled).") - parser.add_argument("--agents-db", default=os.environ.get("CONTEXTUAL_ORCHESTRATOR_AGENTS_DB") or None, - help="Optional sqlite path so runtime agent-pool changes (add/patch/remove) survive restarts.") - parser.add_argument("--provider-ca-bundle", default=os.environ.get("CONTEXTUAL_ORCHESTRATOR_PROVIDER_CA_BUNDLE") or None, - help="Path to a CA bundle used to verify provider TLS (e.g. a corporate gateway root).") + parser.add_argument( + "--clearfolio-url", + default="", + help="Clearfolio viewer URL. When omitted, bootstrap copies CONTEXTUAL_ORCHESTRATOR_CLEARFOLIO_URL into the serve_runtime KV once.", + ) + parser.add_argument( + "--agents-db", + default="", + help="Optional sqlite path for runtime agent-pool changes. When omitted, bootstrap copies CONTEXTUAL_ORCHESTRATOR_AGENTS_DB into the serve_runtime KV once.", + ) + parser.add_argument( + "--provider-ca-bundle", + default="", + help="Provider TLS CA bundle path. When omitted, bootstrap copies CONTEXTUAL_ORCHESTRATOR_PROVIDER_CA_BUNDLE into the serve_runtime KV once.", + ) parser.add_argument("--insecure-skip-tls-verify", action="store_true", help="Dev only: do not verify provider TLS certificates (insecure).") parser.add_argument("--budget-max-output-tokens", type=int, default=None, @@ -94,12 +155,14 @@ def main() -> None: help="Measure orchestration vs a single-worker baseline on these prompts and print the report.") args = parser.parse_args() - client = ModelClient(ca_bundle=args.provider_ca_bundle, verify_tls=not args.insecure_skip_tls_verify) + seed_provider_egress_from_environ() + state_db, agents_db, clearfolio_url, provider_ca_bundle = serve_runtime_paths(args) + client = ModelClient(ca_bundle=provider_ca_bundle, verify_tls=not args.insecure_skip_tls_verify) orchestrator = TaskOrchestrator( load_agents(args.agents), client=client, - state_db=args.state_db, - agents_db=args.agents_db, + state_db=state_db, + agents_db=agents_db, budget_max_output_tokens=args.budget_max_output_tokens, budget_max_cost_usd=args.budget_max_cost_usd, cache_ttl=args.cache_ttl, @@ -110,13 +173,15 @@ def main() -> None: return if args.serve: - if not (args.auth_token or args.admin_token or args.inference_token): + auth_token, admin_token, inference_token = serve_security_tokens(args) + if not (auth_token or admin_token or inference_token): parser.error( "--serve requires --auth-token, split --admin-token/--inference-token, " - "or matching CONTEXTUAL_ORCHESTRATOR_* environment variables" + "or matching CONTEXTUAL_ORCHESTRATOR_* environment variables " + "so bootstrap can copy them into the credential KV" ) - if not args.auth_token and (args.admin_token or args.inference_token) and not ( - args.admin_token and args.inference_token + if not auth_token and (admin_token or inference_token) and not ( + admin_token and inference_token ): parser.error("split token mode requires both --admin-token and --inference-token") serve( @@ -124,13 +189,13 @@ def main() -> None: host=args.host, port=args.port, security=SecurityConfig( - auth_token=args.auth_token, - admin_token=args.admin_token, - inference_token=args.inference_token, + auth_token=auth_token, + admin_token=admin_token, + inference_token=inference_token, allow_public_bind=args.allow_public_bind, expose_trace_by_default=args.expose_trace_by_default, ), - clearfolio_url=args.clearfolio_url, + clearfolio_url=clearfolio_url, ) return diff --git a/contextual_orchestrator/admin.py b/contextual_orchestrator/admin.py index 1f4d1dee0..2b5c3b430 100644 --- a/contextual_orchestrator/admin.py +++ b/contextual_orchestrator/admin.py @@ -42,7 +42,7 @@ "doc_viewer_open": "Open viewer", "doc_viewer_open_doc": "Open document", "doc_viewer_docid": "docId", - "doc_viewer_hint": "Set --clearfolio-url (or CONTEXTUAL_ORCHESTRATOR_CLEARFOLIO_URL) to enable.", + "doc_viewer_hint": "Pass --clearfolio-url, or start once with CONTEXTUAL_ORCHESTRATOR_CLEARFOLIO_URL so the serve_runtime KV can copy it.", "observability_title": "Observability", "spend_title": "Spend", "spend_model": "Model", @@ -276,7 +276,7 @@ "doc_viewer_open": "뷰어 열기", "doc_viewer_open_doc": "문서 열기", "doc_viewer_docid": "docId", - "doc_viewer_hint": "--clearfolio-url (또는 CONTEXTUAL_ORCHESTRATOR_CLEARFOLIO_URL) 설정 시 활성화됩니다.", + "doc_viewer_hint": "--clearfolio-url을 넘기거나, CONTEXTUAL_ORCHESTRATOR_CLEARFOLIO_URL로 한 번 기동해 serve_runtime KV에 복사하세요.", "observability_title": "관측", "spend_title": "비용", "spend_model": "모델", @@ -913,7 +913,7 @@ - + @@ -967,7 +967,7 @@ -

Set --clearfolio-url (or CONTEXTUAL_ORCHESTRATOR_CLEARFOLIO_URL) to enable.

+

Pass --clearfolio-url, or start once with CONTEXTUAL_ORCHESTRATOR_CLEARFOLIO_URL so the serve_runtime KV can copy it.

RuleScopeExclusion
PII-001All agentsMask email, phone
PII-001All agentsRedact credentials; keep operational email
SEC-002workerTool web_search
DATA-003verifierField ip_address
FIN-004synthesizerRecord amount