Skip to content

feat(config)!: one AGENTIC_KIT_ knob header, one SDK-wide log ceiling - #38

Merged
sedawwk merged 1 commit into
masterfrom
feature/unified-config-header
Sep 20, 2026
Merged

sedawwk merged 1 commit into
masterfrom
feature/unified-config-header

Conversation

@heshaoqiong-tuya

@heshaoqiong-tuya heshaoqiong-tuya commented Sep 16, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Every build-time knob in the SDK now lives behind an AGENTIC_KIT_ prefix, with its
default and rationale in its owning subsystem's config file — there is no central
entry file. Logging collapses to one compile-time ceiling for the whole SDK.

Knobs File
Log ceiling + integrator-override pickup common/log.h
PAL FreeRTOS task sizing (3) pal/pal_config_defaults.h
MQTT + ATOP-over-HTTP (6) modules/iot-client/include/iot_client_config_defaults.h
TAI buffers & scheduling (9) modules/rtc-tcp-client/include/tai_config_defaults.h
— none by design (see banner) modules/tuya-ble/include/tuya_ble_config_defaults.h

The override pickup lives in common/log.h because every SDK translation unit includes
it: whichever defaults header a TU reaches first, the integrator's overrides are applied
before any #ifndef default. Each *_config_defaults.h includes log.h first for
exactly that reason, so include order can never matter.

Breaking changes

1. All 20 knob macros renamed with the AGENTIC_KIT_ prefix. The old names collided
for real: coreMQTT's own core_mqtt_config_defaults.h defines a same-named
MQTT_SEND_TIMEOUT_MS (20000U) that fought the SDK's 2000U default by include order, and
LOG_LEVEL is claimed by several platform SDKs. Migration is a mechanical rename:

Old New
LOG_LEVEL / TAI_LOG_LEVEL AGENTIC_KIT_LOG_LEVEL (one ceiling for the whole SDK)
MQTT_MAX_PACKET_SIZE AGENTIC_KIT_MQTT_MAX_PACKET_SIZE
MQTT_SEND_TIMEOUT_MS AGENTIC_KIT_MQTT_SEND_TIMEOUT_MS
MQTT_RECV_TIMEOUT_MS AGENTIC_KIT_MQTT_RECV_TIMEOUT_MS
MQTT_CONNECT_TIMEOUT_MS AGENTIC_KIT_MQTT_CONNECT_TIMEOUT_MS
REQUEST_HEADER_BUFFER_SIZE AGENTIC_KIT_REQUEST_HEADER_BUFFER_SIZE
RESPONSE_BUFFER_SIZE AGENTIC_KIT_RESPONSE_BUFFER_SIZE
TAI_FRAG_BUF_SIZE AGENTIC_KIT_TAI_FRAG_BUF_SIZE
TAI_MAX_FRAGMENT_PAYLOAD AGENTIC_KIT_TAI_MAX_FRAGMENT_PAYLOAD
TAI_TX_HDR_BUF_SIZE AGENTIC_KIT_TAI_TX_HDR_BUF_SIZE
TAI_FRAME_COALESCE_LIMIT AGENTIC_KIT_TAI_FRAME_COALESCE_LIMIT
TAI_TX_CTRL_BUF_SIZE AGENTIC_KIT_TAI_TX_CTRL_BUF_SIZE
TAI_MAX_ATTRS AGENTIC_KIT_TAI_MAX_ATTRS
TAI_DRAIN_BUDGET_MS AGENTIC_KIT_TAI_DRAIN_BUDGET_MS
TAI_WORKER_POLL_CAP_MS AGENTIC_KIT_TAI_WORKER_POLL_CAP_MS
TAI_LOG_MEDIA_SAMPLE_N AGENTIC_KIT_TAI_LOG_MEDIA_SAMPLE_N
PAL_FR_TASK_STACK_WORDS AGENTIC_KIT_PAL_FR_TASK_STACK_WORDS
PAL_FR_TASK_PRIORITY AGENTIC_KIT_PAL_FR_TASK_PRIORITY
PAL_FR_TASK_NAME AGENTIC_KIT_PAL_FR_TASK_NAME

2. One compile-time log ceiling. AGENTIC_KIT_LOG_LEVEL (0=none … 4=debug, default 4)
gates every SDK log macro — iot-client's log_* family, TAI_LOG*,
TUYA_BLE_HAL_LOG*, and the coreMQTT/coreHTTP log routing. Above the ceiling a log line
compiles out entirely: no call, no argument evaluation, no format string in the image.
Below it, the existing runtime filter (log_set_level()) still applies. Module macro
names and output bytes are unchanged (thin re-tags). -DTAI_LOG_LEVEL=N moves to
-DAGENTIC_KIT_LOG_LEVEL=N, noting it now silences the whole SDK.

Also removes the dead IOT_DO_NOT_USE_CUSTOM_CONFIG flag from the ESP-IDF component.

How integrators override (unchanged story, one file reaches everything)

  1. Recommended — create your own agentic_kit_config.h holding only the knobs you
    want to change (plain #define, no #ifndef), put its directory on the include path
    of every target that compiles SDK sources. Picked up automatically via __has_include,
    before every default — the lwIP lwipopts.h / mbedTLS mbedtls_config.h pattern.
  2. -D<NAME>=<value> per knob.
  3. -DAGENTIC_KIT_USER_CONFIG='"my_opts.h"' — arbitrary file name, for toolchains
    without __has_include; when set it wins and the include-path search is skipped.

The SDK never owns a file named agentic_kit_config.h: a quoted include searches the
includer's own directory before the -I path, so the plain name must stay the
integrator's. All knob defaults use #ifndef, so unlisted knobs follow SDK defaults and
upgrades merge cleanly.

Why this shape

  • Buffer sizes are product properties (DP schema size, PSRAM or not, audio frame
    length) — the defaults now sit in the subsystem a reviewer audits for them, each with
    its full rationale (units, couplings, war stories) in the file's comments.
  • One pickup point (common/log.h) keeps the "one override file for everything" story
    while the defaults themselves are distributed — no per-module override files needed.
  • Iron rule: knob values must be identical across every target that compiles SDK
    sources (struct layouts and buffer sizes depend on them; mismatches fail silently).

Full guide — mechanisms, ESP-IDF/CMake wiring, two-layer log model, per-knob tables,
migration: docs-site/docs/guides/compile-time-knobs.md.

Verification

  • cc -dM: all 19 knob macros byte-identical to the pre-change single header (only
    include guards differ); knob values and rationale comments preserved.
  • One agentic_kit_config.h on the include path overrides LOG_LEVEL / MQTT / TAI /
    ATOP / PAL knobs simultaneously; AGENTIC_KIT_USER_CONFIG wins over it; C++ TU
    compiles and links (config sits outside extern "C").
  • Fresh CMake build + ctest: 15/15 at defaults and with an integrator override
    file on the include path; LOG_LEVEL 0–3 compile warning-free, level 0 strips every
    _log_emit reference and log tag string from every object (sole documented exception:
    the tai_pkt_log.c media sampler).
  • Docs site builds clean; three-lens adversarial review (include order, doc pointers,
    behavior preservation) — all findings addressed.

@heshaoqiong-tuya
heshaoqiong-tuya marked this pull request as draft September 16, 2026 06:27
@heshaoqiong-tuya
heshaoqiong-tuya force-pushed the feature/unified-config-header branch 3 times, most recently from 2990947 to ee5bb6d Compare September 17, 2026 09:44
@heshaoqiong-tuya
heshaoqiong-tuya marked this pull request as ready for review September 17, 2026 09:46
@heshaoqiong-tuya
heshaoqiong-tuya force-pushed the feature/unified-config-header branch 6 times, most recently from 5dc8cd7 to 41a7602 Compare September 20, 2026 05:54
heshaoqiong-tuya added a commit that referenced this pull request Sep 20, 2026
…VEL)

Master's log story was split three ways and all three parts were broken:
`-DLOG_LEVEL` was a dead knob (no file referenced it), rtc-tcp-client alone
had a compile gate (`TAI_LOG_LEVEL`), and every other module called
`log_emit()` directly with no compile-time control at all. This stacks on
the config rename (#38 branch) and replaces all of it with one two-layer
model:

- `common/log.h` gains the `log_tag_*` facade (error/warn/info/debug),
  each macro folded away entirely when `AGENTIC_KIT_LOG_LEVEL < n`, and
  the ceiling default lives next to the override pickup. Runtime filtering
  via `log_set_level()` still applies on top of the ceiling.
- Every module re-tags its direct `log_emit()` calls onto the facade:
  common/tls.c, common/rng.c, common/core_mqtt_config.h,
  common/core_http_config.h, pal/pal_posix.c, pal/pal_freertos.c, the
  iot-client log family, tuya_ble_bigdata.c. coreMQTT/coreHTTP keep
  routing into the facade through their custom-config indirection.
- rtc-tcp-client's private `TAI_LOG_LEVEL` gate and `TAI_LOG_` helpers are
  absorbed: tuya_internal.h now maps `TAI_LOGE/W/I/D` onto the facade, so
  existing `-DTAI_LOG_LEVEL=N` users must move to
  `-DAGENTIC_KIT_LOG_LEVEL=N` (BREAKING) -- which now also silences the
  rest of the SDK, not just this module.
- Enforcement is layered where it costs: tai_pkt_log.c compiles its whole
  JSON formatter out below INFO, and packet-log emission dispatches over
  the gated macros by its two runtime levels (the last raw `log_emit()`
  call site). Output bytes are unchanged at default ceilings.
- test_core.c gains test_compile_time_log_ceiling, which builds a TU at
  AGENTIC_KIT_LOG_LEVEL=0 and asserts zero log calls and zero
  argument-evaluation side effects; the `test` CI job now greps the tree
  for raw log_emit/printf call sites outside common/log.{h,c} and the test
  trees, so a new one fails the pipeline.
- Docs: the compile-time-knobs guide gains the Logging section, the
  SDK-wide table row and the LOG_LEVEL / TAI_LOG_LEVEL migration rows in
  both locales; the rtc-tcp-client reference documents the two-layer
  model; AGENTS.md swaps the dead-knob gotcha for the ceiling story.

Verified: ctest 16/16 (incl. the new ceiling test); the CI leak grep
passes clean on this tree and flags an injected probe line (while
fprintf/snprintf-style libc calls stay unflagged); ceiling probes on
tai_pkt_log.c (cc -DAGENTIC_KIT_LOG_LEVEL=0/2 -> 0 log refs and 0
formatter strings, 3/4 -> exactly one of each); `git diff 63c038e` shows
only the CHANGELOG restructure, so the tree is the previously-reviewed
work plus this branch split; docs-site builds clean in both locales.

Co-Authored-By: Claude Code <[email protected]>
Co-Authored-By: Codex <[email protected]>
heshaoqiong-tuya added a commit that referenced this pull request Sep 20, 2026
…VEL)

Master's log story was split three ways and all three parts were broken:
`-DLOG_LEVEL` was a dead knob (no file referenced it), rtc-tcp-client alone
had a compile gate (`TAI_LOG_LEVEL`), and every other module called
`log_emit()` directly with no compile-time control at all. This stacks on
the config rename (#38 branch) and replaces all of it with one two-layer
model:

- `common/log.h` gains the `log_tag_*` facade (error/warn/info/debug),
  each macro folded away entirely when `AGENTIC_KIT_LOG_LEVEL < n`, and
  the ceiling default lives next to the override pickup. Runtime filtering
  via `log_set_level()` still applies on top of the ceiling.
- Every module re-tags its direct `log_emit()` calls onto the facade:
  common/tls.c, common/rng.c, common/core_mqtt_config.h,
  common/core_http_config.h, pal/pal_posix.c, pal/pal_freertos.c, the
  iot-client log family, tuya_ble_bigdata.c. coreMQTT/coreHTTP keep
  routing into the facade through their custom-config indirection.
- rtc-tcp-client's private `TAI_LOG_LEVEL` gate and `TAI_LOG_` helpers are
  absorbed: tuya_internal.h now maps `TAI_LOGE/W/I/D` onto the facade, so
  existing `-DTAI_LOG_LEVEL=N` users must move to
  `-DAGENTIC_KIT_LOG_LEVEL=N` (BREAKING) -- which now also silences the
  rest of the SDK, not just this module.
- Enforcement is layered where it costs: tai_pkt_log.c compiles its whole
  JSON formatter out below INFO, and packet-log emission dispatches over
  the gated macros by its two runtime levels (the last raw `log_emit()`
  call site). Output bytes are unchanged at default ceilings.
- test_core.c gains test_compile_time_log_ceiling, which builds a TU at
  AGENTIC_KIT_LOG_LEVEL=0 and asserts zero log calls and zero
  argument-evaluation side effects; the `test` CI job now greps the tree
  for raw log_emit/printf call sites outside common/log.{h,c} and the test
  trees, so a new one fails the pipeline.
- Docs: the compile-time-knobs guide gains the Logging section, the
  SDK-wide table row and the LOG_LEVEL / TAI_LOG_LEVEL migration rows in
  both locales; the rtc-tcp-client reference documents the two-layer
  model; AGENTS.md swaps the dead-knob gotcha for the ceiling story.

Verified: ctest 16/16 (incl. the new ceiling test); the CI leak grep
passes clean on this tree and flags an injected probe line (while
fprintf/snprintf-style libc calls stay unflagged); ceiling probes on
tai_pkt_log.c (cc -DAGENTIC_KIT_LOG_LEVEL=0/2 -> 0 log refs and 0
formatter strings, 3/4 -> exactly one of each); `git diff 63c038e` shows
only the CHANGELOG restructure, so the tree is the previously-reviewed
work plus this branch split; docs-site builds clean in both locales.

Co-Authored-By: Claude Code <[email protected]>
Co-Authored-By: Codex <[email protected]>
Every build-time knob now has its default and rationale in its owning
subsystem's config file -- no central entry file:

- common/log.h carries the integrator-override pickup
  (__has_include("agentic_kit_config.h") / AGENTIC_KIT_USER_CONFIG). It is
  the one header every SDK translation unit includes, so overrides run
  before every #ifndef default, whichever file that default lives in;
  each defaults file includes log.h first for exactly that reason. The
  SDK owns no file named agentic_kit_config.h: a quoted include searches
  the includer's own directory (common/) before the -I path, so the plain
  name must stay the integrator's (lwIP lwipopts.h / mbedTLS
  mbedtls_config.h / FreeRTOS FreeRTOSConfig.h pattern).
- pal/pal_config_defaults.h: the PAL_FR_TASK_* knobs, included directly
  by pal_freertos.c.
- modules/iot-client/include/iot_client_config_defaults.h (MQTT +
  ATOP-over-HTTP) and modules/rtc-tcp-client/include/tai_config_defaults.h
  (TAI buffers & scheduling), beside each module's public headers.
  tuya-ble ships no defaults file at all: it has zero knobs -- its
  public-struct sizing constants and protocol geometry are port API, not
  build knobs (why, in the tuya_ble_prov.h banner beside those
  constants); the first real tuya-ble knob recreates
  include/tuya_ble_config_defaults.h as AGENTIC_KIT_TUYA_BLE_*.

The iot-client layout mirrors tai's two-header split: the include/
defaults file holds only the AGENTIC_KIT_* #ifndef knob defaults
(log.h first), while the non-knob module data -- release-managed
version strings, the per-region ATOP/MQTT service endpoints,
get_default_pal(), the module's log_* binding and pal_strdup() --
lives in src/iot_internal.h (the former src/iot_config_defaults.h,
restored under its new name and merged with the former 28-line
iot_client_internal.h, so the module keeps one internal header).
Module sources and host tests include iot_internal.h, which pulls the
knob file in like tai_internal.h does. The version data and endpoints
ride releases or the Tuya cloud, not the override mechanism (called
out in the guide). tools/bump_version and the README/guide references
follow the path change; iot_dns.h and iot_on_boarding.h, which leaned
on the old header's transitive includes, now include what they use
directly (iot_client.h, tls.h).

Integrators override per product by: dropping their own
agentic_kit_config.h (only the changed #defines, one file for every
subsystem) on the include path; -D<NAME>=<value> as before; or
-DAGENTIC_KIT_USER_CONFIG='"my_opts.h"' for toolchains without
__has_include (when set it wins and the include-path search is skipped).

BREAKING: the 18 build-time knobs are renamed with an AGENTIC_KIT_
prefix. The old names collided for real: coreMQTT's own
core_mqtt_config_defaults.h defines a same-named MQTT_SEND_TIMEOUT_MS
(20000U) that fought the SDK's 2000U default by include order, and
LOG_LEVEL is claimed by several platform SDKs. Migration is a mechanical
rename of -D flags and override headers; the old->new table is in the
guide below.

The log knobs are deliberately untouched in this change: the dead
LOG_LEVEL knob keeps its name, and TAI_LOG_LEVEL keeps working exactly
as on master (rtc-tcp-client compile gate over direct log_emit calls).
The log_* macros in src/iot_internal.h likewise keep their master form
(direct log_emit, no new gate). Both are absorbed into an SDK-wide
AGENTIC_KIT_LOG_LEVEL by the stacked follow-up branch.

Also derives the DP publish gate (DP_MQTT_MAX_PAYLOAD in iot_dp.c) from
AGENTIC_KIT_MQTT_MAX_PACKET_SIZE instead of mirroring the number, so
raising the knob lifts the gate automatically.

Also removes the dead IOT_DO_NOT_USE_CUSTOM_CONFIG flag from the ESP-IDF
component.

Documents it: docs-site/docs/guides/compile-time-knobs.md (the three
override mechanisms with ESP-IDF/CMake wiring, the all-targets
consistency rule, the design rationale, an 18-knob quick reference and
the migration table), wired into sidebars; the porting guide, ATOP
guide, Unreleased CHANGELOG bullet and the ESP-IDF example's CMakeLists
comment point at the final layout; the guide's English mirror ships with
the same anchors under docs-site/i18n/en/.

Verified: the 18 knob #defines are byte-identical to the pre-split
state (git grep diff, empty); an integrator agentic_kit_config.h
overriding MQTT/TAI knobs simultaneously is picked up (cc probe through
both defaults headers); a C++ TU compiles against the new log.h (pickup
outside extern "C"); no reference to the old header names remains (git
grep, empty) and all former include sites build unchanged; full host
build including examples; ctest 15/15; the docs-site builds clean in
both locales (check:i18n included).

Co-Authored-By: Claude Code <[email protected]>
Co-Authored-By: Codex <[email protected]>
@heshaoqiong-tuya
heshaoqiong-tuya force-pushed the feature/unified-config-header branch from 41a7602 to 408aba9 Compare September 20, 2026 09:57
@sedawwk
sedawwk merged commit 8987cf3 into master Sep 20, 2026
8 checks passed
@heshaoqiong-tuya
heshaoqiong-tuya deleted the feature/unified-config-header branch September 20, 2026 10:34
heshaoqiong-tuya added a commit that referenced this pull request Sep 24, 2026
…ader split

SDK_VERSION moved out of include/iot_client_config_defaults.h into
src/iot_internal.h (#38), so show/next/release all died on the default
path with "no '#define SDK_VERSION' line found". Verified with
show/next --minor/release against the new default header.

Co-Authored-By: Claude Code <[email protected]>
heshaoqiong-tuya added a commit that referenced this pull request Sep 24, 2026
SDK_VERSION agentic-kit_0.4.0 -> agentic-kit_0.5.0 via
tools/bump_version next --minor + release; CHANGELOG Unreleased
becomes [0.5.0] - 2026-09-24 (English edition, #35, #37, #38, #40,
#41 and the UEAZ fix). tuya_iot_client build verified.

Co-Authored-By: Claude Code <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants