Skip to content

refactor: retire motrix_legacy — owner-layer decomposition - #194

Merged
TATP-233 merged 6 commits into
mainfrom
fix/issue-177-retire-motrix-legacy
Apr 10, 2026
Merged

TATP-233 merged 6 commits into
mainfrom
fix/issue-177-retire-motrix-legacy

Conversation

@TATP-233

@TATP-233 TATP-233 commented Apr 10, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Closes #177. Decomposes the motrix_legacy compatibility bag into three owner-layer mechanisms:

  • Reward: Route all backend-specific rewards through reward@reward_motrix Hydra defaults. No more reward_config embedded in the bag.
  • Algo: New algo_motrix sidecar config group (4 YAML files). BackendAdapter.resolve_algo_dict() merges the sidecar over cfg.algo — cfg.algo is never mutated.
  • Env: Backend-specific env overrides via backend_profile with flat typed fields. Applied by registry.make — no apply_*() methods.

Removed

  • motrix_legacy config key from all 5 task YAMLs
  • LegacyMotrixProfile + apply_legacy_motrix_profile() from Go1/Go2
  • backend_overrides: dict + apply_backend_overrides() from G1
  • _use_legacy_motrix_baseline() — replaced by direct backend_type check
  • adapt_legacy_go1_motrix_obs_if_needed() from off-policy worker
  • Runtime mutation of cfg.algo

Added

  • conf/{ppo,offpolicy}/algo_motrix/*.yaml sidecar configs
  • BackendAdapter.resolve_algo_dict() — pure function, returns resolved dict
  • Commands.vel_limit as proper dataclass field

21 files changed, 348 insertions(+), 434 deletions(-).

Test plan

  • make test — 344 passed, 12 skipped, 0 failures
  • Zero hits for motrix_legacy, legacy_motrix, adapt_legacy, apply_backend_overrides
  • Tests assert resolved config values, NOT cfg.motrix_legacy.* structure
  • Tests verify cfg.algo is NOT mutated after resolve_algo_dict()

@TATP-233

Copy link
Copy Markdown
Collaborator Author

这版不能合并。#177 要求退役的是跨层 compatibility bag,本 PR 只是把 motrix_legacy 改名成 backend_profile,owner-layer 问题原样保留。

按仓库当前架构,这个 PR 的核心问题有四个:

  1. src/unilab/training/backend_adapter.py:34-62 仍然在训练入口按 training.sim_backend == "motrix" 读取 backend_profile,然后 runtime mutation cfg.algo,再拼 env_cfg_override。这和 Work: 退役 motrix_legacy 兼容层 — 用显式 backend/profile 配置替代跨层补丁包 #177 明确要求的“停止在训练脚本中直接做运行时 mutation”“compose 后配置即真相”正面冲突。名字换了,机制没变。

  2. task YAML 仍然把多种不同语义塞进同一个 bag。比如 conf/ppo/task/go1_joystick.yaml:14-41 同时混了 algo_overrides、motrix_profile、reward_config;conf/ppo/task/g1_joystick.yaml:15-61 又把 reward_config、backend_overrides、algo tuning 混在一起。这不是替代设计,只是 rename 后继续固化结构污染。

  3. env owner 层的 legacy 分支并没有被移除,只是改了名字。src/unilab/envs/locomotion/go1/joystick.py:35-72 仍有 MotrixProfile.enabled + apply_motrix_profile();src/unilab/envs/locomotion/g1/joystick.py:113-141,181-184,224-230 仍有 backend_overrides / apply_backend_overrides() / _use_motrix_baseline()。backend-specific 行为仍靠运行时开关补丁,不是显式 schema。

  4. 测试继续把这个 compatibility bag 固化成公共 contract。tests/scripts/test_train_scripts.py:153-306 现在断言的是 cfg.backend_profile.* 和 build helper 的 mutation 结果,而不是新的 schema / compose 结果 / env 行为。Work: 退役 motrix_legacy 兼容层 — 用显式 backend/profile 配置替代跨层补丁包 #177 的 DoD 明确要求反过来。

正确执行方案不要再做 rename-only,而是按 owner layer 拆:

  1. reward 先独立收敛。所有 backend-specific reward 差异统一走现有 reward_{backend} 机制;Go1 / G1 / offpolicy 里还挂在 backend_profile.env_cfg_override.reward_config 的内容,迁到 conf/*/reward/*_motrix.yaml,脚本只保留 extract_reward_config() 这条路径。

  2. algo tuning 独立成显式 backend-scoped config,而不是 runtime 改 cfg.algo。可以参考 reward_motrix 的模式,新增可类型化的 sidecar(例如 algo_motrix 或等价 schema),训练入口做“选择”而不是“改写”。关键是不再 mutate cfg.algo。

  3. env/backend 差异回 owner layer。Go1/Go2 的 command_vel_limit、G1 的 control_action_scale / gait_phase_init_mode / reset_base_qvel_limit / command_vel_limit,要么升成 env cfg 的正式字段,要么放进显式、类型化的 backend env profile;删除 motrix_profile.enabled、backend_overrides: dict、apply_* 这类 bag-style 入口。

  4. 在上面三步落地后,再删 worker/env 的 legacy 适配分支,并把测试改成:验证 compose 结果、resolved reward/algo/env config、以及 MuJoCo/Motrix 的近边界行为;不要再断言任何 cfg.backend_profile.* 结构存在。

  5. motrix_play_only 是 play-only 机制,可以单独保留或后续按同样 sidecar 模式整理,但不要继续和训练期 backend 兼容逻辑混在一个 bag 里。

结论:这版不是 partial fix,而是 rename-only。请按上面的 owner-layer 方案重做,不建议在这个 PR 上继续修补。

@Kuriharamio
Kuriharamio force-pushed the fix/issue-177-retire-motrix-legacy branch from 14bcede to d7b6919 Compare April 10, 2026 09:06
@TATP-233 TATP-233 changed the title refactor: retire motrix_legacy — rename to backend_profile refactor: retire motrix_legacy — owner-layer decomposition Apr 10, 2026
Decompose the `motrix_legacy` compatibility bag into three owner-layer
mechanisms, following the existing `reward_motrix` sidecar pattern:

**Reward** — route all backend-specific rewards through `reward@reward_motrix`
Hydra defaults. Motrix reward YAML files already existed; task YAMLs now
reference them via defaults list instead of embedding reward_config in
the bag. `extract_reward_config()` remains the single reward resolution path.

**Algo** — new `algo_motrix` sidecar config group (4 files under
`conf/{ppo,offpolicy}/algo_motrix/`). `BackendAdapter.resolve_algo_dict()`
merges the sidecar over `cfg.algo` and returns a new dict — cfg.algo is
never mutated. Training scripts read from the resolved dict. Supports
`applies_to.algo` filtering (e.g., SAC-only overrides for Go1 offpolicy)
and CLI explicit-key precedence.

**Env** — backend-specific env overrides in `backend_profile` section of
task YAML, using flat fields that match env cfg structure (e.g.,
`commands.vel_limit`, `control_config.action_scale`, `gait_phase_init_mode`).
Applied via `registry.make` env_cfg_override — no `apply_*()` methods.

Removed:
- `motrix_legacy` config key from all 5 task YAMLs
- `LegacyMotrixProfile` dataclass + `apply_legacy_motrix_profile()` from Go1/Go2
- `backend_overrides: dict` + `apply_backend_overrides()` from G1
- `_use_legacy_motrix_baseline()` — replaced by direct `backend_type` check
- `adapt_legacy_go1_motrix_obs_if_needed()` from off-policy worker (dead code)
- Runtime mutation of `cfg.algo` by BackendAdapter

Updated:
- Commands.vel_limit → proper dataclass field (enables registry.make override)
- Training scripts use resolved algo dict instead of cfg.algo
- Tests assert resolved config values, not bag structure
- Architecture docs updated
@Kuriharamio
Kuriharamio force-pushed the fix/issue-177-retire-motrix-legacy branch from d7b6919 to 4511aa3 Compare April 10, 2026 09:13
@TATP-233

Copy link
Copy Markdown
Collaborator Author

补一条更具体的阻塞意见:这版不仅还有 backend leak,而且已经引入了最终生效超参数偏差。

  1. conf/offpolicy/algo_motrix/、conf/ppo/algo_motrix/ 这样的目录和命名本身就不应该出现。
    这说明“算法参数差异”仍然被建模成了一个显式的 backend 分支,并且后续还要靠 BackendAdapter.resolve_algo_dict() 去解释它。这样训练层依然知道 motrix,算法层并没有和后端层完全解耦。

  2. 更严重的是,这次重构已经改掉了原本实际生效的超参数。g1_joystick 就是一个直接例子:

    • 之前 PR 版本里,Motrix 路径的 algo override 明确有 max_iterations: 151:
      conf/ppo/task/g1_joystick.yaml old lines 17-19
    • 现在拆分后,conf/ppo/algo_motrix/g1.yaml:1-9 里没有这个 override;
    • 同时 conf/ppo/task/g1_joystick.yaml:9-16 的 base algo 还是 max_iterations: 220。

    结果就是:现在 Motrix G1 PPO 的最终生效 max_iterations 已经从原来的 151 漂到了 220。这不只是命名问题,而是行为回退。按照这次整改要求,重构后最终生效的超参数不能和原本存在偏差。

  3. 这也暴露出当前方案的核心问题:你现在并不是在“保持行为不变地重构配置表达”,而是在“重新组织配置 + 由 Python 逻辑决定哪些 override 生效”。只要这条链路还存在,就很容易漏掉这种超参数漂移。

我这里的要求很明确:

  1. 不要再保留 algo_motrix 这种 backend-specific 目录/命名。
  2. 算法超参数差异必须通过纯 YAML compose 进入最终 cfg.algo,训练代码不再解释 backend-specific algo sidecar。
  3. 在继续改之前,先把所有迁移后的最终生效 hyperparams 和旧行为逐项对齐,至少把当前已经发现的 g1_joystick Motrix max_iterations 偏差修正掉。
  4. 测试也要补成“验证迁移前后 resolved hyperparams 等价”,而不只是验证新 helper 返回了某个 dict。

在这个问题解决之前,这个 PR 还不能算达到要求。

Address review feedback on rev 2:

- Delete conf/{ppo,offpolicy}/algo_motrix/ directories entirely
- Delete BackendAdapter.resolve_algo_dict() — training code no longer
  interprets backend-specific algo config; cfg.algo comes straight from
  Hydra compose
- Fold all old motrix_legacy.algo_overrides into the base algo section
  of each task YAML (Go2 pattern), preserving the exact pre-refactor
  resolved hyperparams for the Motrix path:
    * Go1 PPO: empirical_normalization, policy, algorithm overrides
    * G1 PPO: max_iterations=151 (was drifted to 220), empirical_norm,
      obs_groups.actor, policy, algorithm overrides
    * Go1 offpolicy SAC: num_envs=4096, max_iterations=2000
    * G1 SAC: use_symmetry=true (resolve_sac_use_symmetry in script
      still disables it for motrix at the one-liner level)
- Revert training scripts to read cfg.algo directly
- Rewrite tests to assert resolved hyperparam equivalence with the old
  Motrix behavior, not the internal bag structure
- Update docs to remove algo_motrix references
@TATP-233

Copy link
Copy Markdown
Collaborator Author

这版还是不能过,原因不是 algo_motrix 命名了,而是你现在用“把旧 Motrix 参数直接折进 base task”的方式消除了 sidecar,结果把原本其他路径的生效超参数也改掉了。

这违反了你这轮整改里最关键的约束:重构后最终生效的超参数不能偏离原本已存在的行为。

我确认到的具体回退有:

  1. conf/offpolicy/task/go1_joystick.yaml:8-10 现在把 num_envs=4096、max_iterations=2000 写进了 base algo。
    但在 origin/main,这两个值原本只在 motrix_legacy.applies_to.algo=sac 时对 SAC 生效,base 值仍然是 2048 / 3000:
    origin/main:conf/offpolicy/task/go1_joystick.yaml:7-16

    这意味着现在至少有两个路径被改坏了:

    • 默认 MuJoCo Go1 SAC 配置被从 2048/3000 改成了 4096/2000
    • algo=td3 task=go1_joystick 也会吃到 4096/2000,而旧逻辑里 applies_to.algo=sac 明确会跳过 TD3

    之前的 regression 测试正是在保护这件事:test_offpolicy_build_task_motrix_env_cfg_override_skips_td3。现在这个 guard 被删掉了,但行为问题并没有消失。

  2. conf/ppo/task/go1_joystick.yaml:8-20 现在把 empirical_normalization=true、policy.init_noise_std=0.5、algorithm.learning_rate=3e-4、algorithm.entropy_coef=1e-3 全部折进了 base algo。
    但在 origin/main,这些原本是 motrix_legacy.algo_overrides,只在 Motrix 路径生效:
    origin/main:conf/ppo/task/go1_joystick.yaml:14-23

    也就是说,现在默认 MuJoCo Go1 PPO 的生效超参数已经被改掉了。

  3. conf/ppo/task/g1_joystick.yaml:8-21 现在把 max_iterations=151、empirical_normalization=true、obs_groups.actor=[policy]、policy.init_noise_std=0.5、algorithm.* 全部折进了 base algo。
    但在 origin/main,这些原本也是 Motrix-only override:
    origin/main:conf/ppo/task/g1_joystick.yaml:17-27

    这同样把原本 MuJoCo G1 PPO 的 base 行为改掉了。尤其是:

    • max_iterations 从 220 变成 151
    • obs_groups.actor 从 actor 变成 policy

    这已经不是简单调参,而是训练配置语义变化。

  4. 另外,算法层和后端层的彻底解耦也还没完全做到。scripts/train_offpolicy.py:81-83 里的 resolve_sac_use_symmetry() 仍然在训练脚本里显式用 training.sim_backend == "mujoco" 决定算法行为。即便你去掉了 algo_motrix,这里仍然是 algo-layer backend branching。

我这里的结论很明确:

  1. 删除 algo_motrix 是对的,但不能通过把 Motrix-only 参数直接折进 base task 来完成。
  2. 如果某组超参数原本只对 Motrix 或只对 SAC 生效,重构后也必须保持这个作用域,不允许顺手把 MuJoCo / TD3 / 默认 compose 结果改掉。
  3. 这类作用域信息必须继续在 YAML 层表达清楚,而不是通过“把一种 backend 的值升级为全局 base”来偷渡。
  4. 测试需要恢复并扩展为“迁移前后各组合路径等价”,至少覆盖:
    • Go1 offpolicy SAC / TD3 在 MuJoCo 与 Motrix 下的 resolved hyperparams
    • Go1 PPO 在 MuJoCo 与 Motrix 下的 resolved hyperparams
    • G1 PPO 在 MuJoCo 与 Motrix 下的 resolved hyperparams

如果团队决定要正式改变 MuJoCo 的默认超参数,那应该是单独 issue / 单独 PR / 单独验证,不应该混在这次 compatibility-layer 退役里。

@TATP-233
TATP-233 force-pushed the fix/issue-177-retire-motrix-legacy branch from b2898ca to 0b06fad Compare April 10, 2026 14:39
@TATP-233

Copy link
Copy Markdown
Collaborator Author

这版还有一个明确的 contract blocker,所以我这边仍然不能放行。

问题是:你现在把 training.sim_backend 的显式 override 从“受支持的配置输入”改成了“直接报错”,而且测试把这个 breaking change 固化了。

证据:

  1. src/unilab/training/backend_adapter.py:37-47 新增了 _assert_backend_choice_consistent(),当 task owner 文件里的 _selected_sim_backend 和 training.sim_backend 不一致时,直接抛错:
    Use task=... to switch backends instead of overriding training.sim_backend directly.

  2. build_task_env_cfg_override() 在入口处无条件调用这个检查:
    src/unilab/training/backend_adapter.py:49-55

  3. 测试明确把“拒绝 direct training.sim_backend override”当成正确行为:
    tests/training/test_training_helpers.py:129-138

但这和当前仓库自己声明的 config contract 是冲突的。你刚改过的 AGENTS.md 里仍然写着:

  • training.sim_backend 和 backend_profile 必须尊重显式 override。

也就是说,现有 contract 是“显式 override 要被尊重”,不是“显式 override 一律非法”。

这不是实现偏好问题,而是一个 user-facing breaking change:

  • 旧的调用方式 task=... training.sim_backend=motrix
  • 以及围绕这个 contract 建立的脚本/文档/用户习惯

在这版里都被改成了 hard failure,而 issue #177 本身并没有授权顺手废掉这个输入 contract。

我的结论:

  1. 用 backend-specific task owner 文件来表达最终配置,可以接受。
  2. 但不能通过 _selected_sim_backend + hard error 的方式把 training.sim_backend override 判成非法。
  3. 如果要保留 training.sim_backend 这个公开配置项,它就必须继续是有效输入,并与最终 compose 结果保持一致。
    至少不能出现“用户显式 override 了 training.sim_backend,结果训练入口直接拒绝”的行为。
  4. 相应地,tests/training/test_training_helpers.py:129-138 这条测试方向也是错的,不应该把 rejection 固化成 contract。

在这个点修正之前,我这边仍然认为 PR #194 没达到要求。

@TATP-233

Copy link
Copy Markdown
Collaborator Author

我按这个方向补了一版,commit: 2e7606a。

这次没有把 training.sim_backend 恢复成独立 backend switch,而是把 contract 明确定义为:

  1. 后端选择只通过 task=<task>/<backend> 完成。
  2. training.sim_backend 是 task owner YAML 的身份字段,用来标识最终选择的后端。
  3. task=.../mujoco training.sim_backend=motrix 这类组合会产生“Motrix 物理后端 + MuJoCo reward/env/algo”的混合配置,所以应继续被拒绝,而不是被当成合法切换方式。

已同步更新:

  • BackendAdapter 的 guard 和错误信息,避免把它表达成不尊重 override,而是表达为 task owner identity 不一致。
  • tests/training/test_training_helpers.py 的测试命名和断言,覆盖 mixed task/backend identity。
  • AGENTS.md、docs/zh_CN/00-development-architecture.md、docs/zh_CN/02-simulation-backends.md、docs/zh_CN/03-training.md,明确 task 是后端选择入口,training.sim_backend 不能单独用来切后端。

验证:

  • uv run pytest tests/training/test_training_helpers.py tests/config/test_config_system.py
  • make check

@TATP-233

Copy link
Copy Markdown
Collaborator Author

已把分支同步到最新 origin/main,并在 merge commit 03c1743 里把 main 新引入的 conf 变化重新收敛回 task owner 配置。

处理点:

  • main 的 conf/ppo/task/g1_joystick.yaml 新增了 Motrix iterations: 3;现在迁到单一 owner 文件 conf/ppo/task/g1_joystick/motrix.yaml。
  • main 的 conf/offpolicy/task/go2_joystick.yaml 新增了 Motrix domain_rand override;现在拆到 conf/offpolicy/task/sac/go2_joystick/motrix.yaml 和 conf/offpolicy/task/td3/go2_joystick/motrix.yaml,保持 algo/backend 作用域明确。
  • 没有恢复 conf/*/reward、conf/*/sim_backend、motrix_legacy、backend_profile 或 reward_motrix 这类分裂/兼容 bag 结构;后端选择仍然只走 task=<task>/<backend>。
  • 补了 compose 与 env_cfg_override 近边界测试,覆盖 G1 Motrix iterations 和 offpolicy Go2 Motrix domain_rand。

验证:

  • uv run pytest tests/config/test_config_system.py tests/scripts/test_train_scripts.py tests/training/test_training_helpers.py
  • uv run pytest tests/scripts/test_train_scripts.py::test_build_ppo_env_cfg_override_applies_go2_motrix_reward
  • make check

@TATP-233

Copy link
Copy Markdown
Collaborator Author

只看当前这版修改本身,我认为还需要做几处定点收口,问题主要不在超参数内容,而在整体设计边界还不够干净。

  1. 现在的 backend identity 仍然是“双重真相 + Python 裁判”。
    src/unilab/training/backend_adapter.py:37-48 里 _selected_sim_backend 和 training.sim_backend 同时存在,再由 _assert_task_backend_identity() 在训练入口做一致性检查并决定是否合法。
    如果 owner YAML 已经是最终配置入口,就不应该再额外引入一个隐藏 marker,然后让训练层在 compose 之后二次解释 backend 选择语义。这样会把配置 contract 重新拉回 Python 层。

  2. play 路径还在做 post-compose runtime mutation。
    src/unilab/training/backend_adapter.py:70-76 里 motrix_play_only.training_overrides 会直接改写 self.cfg.training。这说明 compose 后配置还不是最终真相,训练入口仍然在补写配置。
    如果这些值确实是 play 模式需要的默认值,建议把它们提前收进 YAML compose(例如独立 play profile / mode config),adapter 只消费 resolved config,不再回写 cfg。

  3. training 层仍然显式知道 backend 名称和 backend 专属机制。
    src/unilab/training/backend_adapter.py:61-67 还在通过 _is_motrix() 和 motrix_play_only 判断走哪条路径;conf/ppo/task/g1_motion_tracking/motrix.yaml:16-28 也把 play-only 机制直接命名成 motrix_play_only。
    这意味着训练层绑定的是具体 backend 名称,而不是能力。更干净的做法应该是抽成 backend-agnostic 的 play_profile / render_profile,训练层只看有没有这个 capability,不看是不是 Motrix。

  4. 测试正在把内部 workaround 固化成公共结构。
    tests/config/test_config_system.py:147-161 目前要求每个 task 文件都必须带 _selected_sim_backend,这会把当前这层隐藏 marker 永久变成受保护结构。
    我建议这里改成只断言外部可观察结果:compose 后的 cfg.training.sim_backend、cfg.algo、cfg.reward、cfg.env 和相关边界行为,而不是断言内部 marker 必须存在。

补一句平衡判断:我 spot check 了 Go1 offpolicy / Go1 PPO / G1 PPO,这版 owner YAML 基本已经把我之前指出的超参数漂移收回来了。所以现在不建议再大改 task/algo/reward 的组织方式,重点就是把上面这几处“隐藏身份字段 + runtime mutation + backend 名称泄漏 + 测试固化内部实现”收干净。

@TATP-233

Copy link
Copy Markdown
Collaborator Author

已按最新 review 做定点收口,commit: 152e838。

处理点:

  • 删除所有 task owner YAML 里的 _selected_sim_backend,不再保留隐藏 backend marker,也移除了 BackendAdapter 里的 backend identity Python 裁判。
  • tests/config/test_config_system.py 改成只保护 task owner 的可观察身份字段,并显式防止 _selected_sim_backend 回流;不再要求内部 marker 存在。
  • motrix_play_only 改成 backend-agnostic 的 play_profile,训练层不再通过 _is_motrix() 或 Motrix 专属字段判断 play 行为。
  • 删除 training_overrides runtime mutation;G1 motion tracking Motrix 的 play_env_num: 128 直接进入 owner YAML 的 training,adapter 只消费 resolved config。
  • play profile 的 env/scene override 只作为 play capability 被消费,不再回写 cfg.training。

验证:

  • uv run pytest tests/config/test_config_system.py tests/scripts/test_train_scripts.py tests/training/test_training_helpers.py
  • make check

@TATP-233

Copy link
Copy Markdown
Collaborator Author

这次我重新按最新提交看了一遍,前面几轮卡住的设计问题基本都已经收干净了,这版我这边没有新的 blocker。

我确认到的关键点是:

  1. src/unilab/training/backend_adapter.py 里已经去掉了 _selected_sim_backend / identity guard,也没有再对 cfg.training 做 post-compose runtime mutation;现在就是消费 compose 后的 reward / env / play_profile。
  2. conf/ppo/task/g1_motion_tracking/motrix.yaml 里的 play-only 配置也已经从 motrix_play_only 收敛成更通用的 play_profile,而且 play_env_num 这类值已经前移到 owner YAML,不再靠 Python 回写。
  3. tests/config/test_config_system.py 现在改成了显式断言 task 文件里不应再有隐藏 backend marker,并继续验证 compose 后的 resolved config / hyperparams / env override 行为。
  4. scripts/train_offpolicy.py 里之前那条按 backend 决定算法行为的 use_symmetry 分支也已经去掉,算法层这边回到了 YAML owner 配置。

现在还能看到的 if cfg.training.sim_backend == "motrix" 主要都在 play/render 路径里,用来区分 Motrix 的交互 renderer 和 MuJoCo 的视频导出。我认为这属于合理的能力边界,不再是之前那种训练/算法配置层泄漏。

所以按当前这版修改本身来看,我这边可以过。

@TATP-233
TATP-233 merged commit 56a6c5a into main Apr 10, 2026
5 checks passed
@TATP-233
TATP-233 deleted the fix/issue-177-retire-motrix-legacy branch April 10, 2026 15:48
TATP-233 added a commit that referenced this pull request May 4, 2026
…gacy

refactor: retire motrix_legacy — owner-layer decomposition
TATP-233 added a commit that referenced this pull request May 30, 2026
…gacy

refactor: retire motrix_legacy — owner-layer decomposition
TATP-233 added a commit that referenced this pull request Sep 4, 2026
…gacy

refactor: retire motrix_legacy — owner-layer decomposition
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.

Work: 退役 motrix_legacy 兼容层 — 用显式 backend/profile 配置替代跨层补丁包

2 participants