Skip to content

core/config: DonkeyConfig resolution order and all-at-once validation #10

Closed

Description

@amirkhan-ak-sf

Summary

DonkeyConfig is the single frozen dataclass every other module reads. Two
behaviours matter more than the field list:

Resolution order (§2.1): explicit kwarg → env var → .donkey-kit.toml
(cwd or $XDG_CONFIG_HOME) → default. We never read .env implicitly;
document that the user calls load_dotenv() themselves.

validated() reports every missing field at once. A config error that
surfaces one missing variable per run is the most common source of
first-five-minutes abandonment, so this is a correctness requirement, not a
nicety.

The control-plane credential and the LLM-proxy credential are deliberately not
conflated
(§2.2) — a developer may legitimately have proxy access and no
Exchange access — so validated(need=...) takes "control_plane" or "llm" and
only checks that capability's fields.

Current state — built

Implemented in python/src/donkey_kit/core/config.py. Field set reflects the
live-verified proxy contract: the proxy authenticates on a
client_id/client_secret request-header pair, not a bearer token, so the
config carries llm_proxy_client_id / llm_proxy_client_secret, and
llm_proxy_key exists only to fill the OpenAI SDK's mandatory api_key slot.

Python

from donkey_kit import Donkey, DonkeyConfig
from donkey_kit.core.errors import ConfigError

# 1. Env-driven, the normal path.
cfg = DonkeyConfig.from_env()

# 2. Explicit, overriding the environment.
cfg = DonkeyConfig(
    llm_proxy_url="https://ingress-gw-abc.usa-e2.cloudhub.io/openai-sdk/",  # no /v1
    llm_proxy_client_id="…",
    llm_proxy_client_secret="…",
    application_name="checkout-agent",
    business_group="payments",
    timeout_s=30.0,
    max_retries=5,
)

# 3. Derived copy, e.g. to point one call at another environment.
prod = cfg.with_overrides(environment="Production")

# 4. Validation names EVERY missing field, with its env var, in one raise.
try:
    DonkeyConfig().validated(need="llm")
except ConfigError as e:
    print(e)
    # Configuration for 'llm' is incomplete. Missing:
    #   - llm_proxy_url (env MULESOFT_LLM_PROXY_URL)
    #   - llm_proxy_client_id (env MULESOFT_LLM_PROXY_CLIENT_ID)
    #   - llm_proxy_client_secret (env MULESOFT_LLM_PROXY_CLIENT_SECRET)

donkey = Donkey(cfg)

.donkey-kit.toml, committed to the repo — only keys that are real
DonkeyConfig fields are accepted, so a typo is dropped rather than silently
misapplied:

[donkey]
environment = "Sandbox"
region = "us"
application_name = "checkout-agent"
business_group = "payments"
registry_cache_ttl_s = 600

TypeScript

import { Donkey, DonkeyConfig, ConfigError, loadConfig } from "@donkey-kit/core";

// 1. Env-driven.
const cfg = loadConfig();

// 2. Explicit. Same resolution order: arg > env > .donkey-kit.toml > default.
const explicit = new DonkeyConfig({
  llmProxyUrl: "https://ingress-gw-abc.usa-e2.cloudhub.io/openai-sdk/",
  llmProxyClientId: "…",
  llmProxyClientSecret: "…",
  applicationName: "checkout-agent",
  businessGroup: "payments",
  timeoutMs: 30_000,
  maxRetries: 5,
});

// 3. Validation is one throw listing everything, matching Python.
try {
  new DonkeyConfig({}).validated("llm");
} catch (e) {
  if (e instanceof ConfigError) console.error(e.message);
  // Configuration for 'llm' is incomplete. Missing:
  //   - llmProxyUrl (env MULESOFT_LLM_PROXY_URL)
  //   - llmProxyClientId (env MULESOFT_LLM_PROXY_CLIENT_ID)
  //   - llmProxyClientSecret (env MULESOFT_LLM_PROXY_CLIENT_SECRET)
}

const donkey = new Donkey(explicit);

The TS port must keep the same env var names so a repo can run a Python agent
and a TS agent off one .env, and expose camelCase fields with the snake_case
env vars — do not invent LLM_PROXY_URL-style aliases.

Acceptance criteria

  • Resolution order kwarg → env → toml → default
  • .env never read implicitly
  • validated() reports all missing fields for the requested capability at once
  • Unknown region raises ConfigError naming the valid set
  • Malformed .donkey-kit.toml raises; a missing one is fine
  • Unknown toml keys are ignored rather than applied
  • TypeScript port with identical env var names and error text
  • Docs page enumerating every field, its env var and its default

Blocked on

region only resolves us today. EU / CA / JP Hyperforce host variants are
UNVERIFIED — see Verify Anypoint control-plane region hosts for EU, CA and JP.

References

  • python/src/donkey_kit/core/config.py, python/tests/unit/test_config.py
  • Build plan §2.1

Activity

  1. amirkhan-ak-sf commented on Sep 4, 2026

    @amirkhan-ak-sf
    CollaboratorAuthor

    Remapped by the phase-based backlog rebuild.

    FabricConfig resolution order and all-at-once validation survive unchanged — build guide 1.1 explicitly keeps them.

  2. changed the title [-]core/config: FabricConfig resolution order and all-at-once validation[/-] [+]core/config: DonkeyConfig resolution order and all-at-once validation[/+] on Sep 11, 2026
  3. added
    area:skeletonDonkeyAsyncClient, config, transport hooks, Donkey facade (1.1)
    choreBuild/tooling/refactor, no user-facing behavior change
    priority/p2Meaningful improvement, ship soon
    size/s~half day, one file
    on Sep 15, 2026
  4. tommasobolis commented on Sep 20, 2026

    @tommasobolis
    Collaborator

    Closing: the Phase-1 Python deliverable is implemented and tested in core/config.py (from_env() resolution order, all-at-once validated(need=...), unknown-region/malformed-toml raises, unknown-key drop) with tests/unit/test_config.py green, and website/content/reference/configuration.mdx documents the fields. The only unchecked item is the TypeScript port, which is Phase 5 (0.1.0 does not ship TS) — not a Phase 1 blocker.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:skeletonDonkeyAsyncClient, config, transport hooks, Donkey facade (1.1)choreBuild/tooling/refactor, no user-facing behavior changepriority/p2Meaningful improvement, ship soonsize/s~half day, one file

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions