Skip to Content
DesignDeklarative Agent-Definitionen — Port von Claude Code 2.1.168

Deklarative Agent-Definitionen — Port von Claude Code 2.1.168

Internes Designdokument für den Port des Schemas für deklarative Agenten (Markdown + YAML-Frontmatter) von Claude Code zu qwen-code. Dies adressiert Issue #4821  und koordiniert den Workflow-Port in Issue #4721  / PR #4732 .

Implementierungsstatus (Vertical Slicing)

PR #4842  hat die Felder seinerzeit mit einem End-to-End-Laufzeitpfad ausgeliefert. PR #4870  hat dann den YAML-Parser ersetzt, um Block-Scalars zu unterstützen. Dieser nachfolgende PR baut auf beiden auf: Er ersetzt den YAML-Stringifier (PR #4870 hat ihn manuell implementiert gelassen — siehe docs/design/yaml-parser-replacement.md), exponiert mcpServers + hooks auf SubagentConfig und bindet sie in die Runtime ein, sodass MCP-Server und Hooks pro Agent tatsächlich ausgelöst werden, wenn ein Subagent läuft.

FeldStatusHinweise
permissionModeshipped (#4842)leitet zur Parse-Zeit auf das bestehende qwen approvalMode um
maxTurnsshipped (#4842)in den bestehenden runConfig.max_turns Runtime-Pfad eingebunden
color Allowlistshipped (#4842)straft das bestehende Feld auf den _Y-Set von CC + auto Legacy-Sentinel-Handling
mcpServersshipped (follow-up)verschachtelter YAML-Roundtrip sicher durch eemeli/yaml stringify; Runtime-Override merged Session- + Agent-Server über Subagent-Config-Wrapper + erzwungenen Tool-Registry-Rebuild
hooksshipped (follow-up)flüchtige HookRegistry-Einträge werden beim Subagent-Spawn registriert und via onStop entfernt; v1 feuert global (kein Agent-Scope-Filter)
effortdeferrednoch kein effort-Parameter auf Model-Layer in qwen-Providern vorhanden
memorydeferredqwen’s Auto-Memory unterscheidet noch nicht zwischen user/project/local Scope
isolationdeferredWorkflow-PR #4732 besitzt die Runtime; der Standardwert pro Agent wird eingeführt, wenn dieser PR landet
initialPromptdeferrederfordert --agent CLI-Flag (keine Main-Session-Agent-Infrastruktur in qwen)
skillsdeferrederfordert Konsumierung von config.skills durch den SkillManager

Die vollständige Reverse-Engineering-Dokumentation unten wird als Design-Referenz für die zurückgestellten Felder beibehalten — Schema-Konstanten, DL7/Ig5-Semantik, Fehlermeldungen und die Koordinierungsmatrix mit dem Workflow sind für diese Arbeit weiterhin tragend.


Phase 0 — Abgrenzung

PunktWert
Zuletzt verifizierter UpstreamClaude Code 2.1.168 (Issue #4821 referenziert ≥ 2.1.167, wir sind einen Bump höher)
Natives Binary/private/tmp/cc-2.1.168/package/claude (220 MB)
Strings-Extrakt/private/tmp/cc-2.1.168/claude.strings (~342 k Zeilen)
Worktree.claude/worktrees/gifted-hamilton-684741
Branchlazzy/gifted-hamilton-684741 ab main @ 45efb1d3a
Nicht im ScopePR #4732 Workflow-Code (separates Worktree lazzy/lucid-pare-974192) — nur über Schnittstelle koordinieren
AutorenregelAutor ist LaZzyMan; keine Co-Authored-By- oder AI-Tooling-Trailers in Commits, PRs, Issues oder Kommentaren (gemäß ~/.claude/CLAUDE.md)

Phase 1 — Reverse-Engineering-Erkenntnisse

Alle Aussagen hier wurden unabhängig gegen claude.strings gegrept und haben einer adversarischen Widerlegung standgehalten. Konfidenzlevel: C = Confirmed (direkte Binär-Evidenz), I = Inferred (aus mehreren bestätigten Fakten synthetisiert), O = Open (noch unsicher).

Schema — die 15 Felder, widerlegt und erneut bestätigt

Das Shadow-Schema für das Agent-Frontmatter ist Ig5, verwendet innerhalb von ug5.agent für die tengu_frontmatter_shadow_unknown_key / _mismatch-Telemetrie. Der Produktions-Loader ist DL7 (parseAgentFromMarkdown), der eine manuell implementierte Validierung pro Feld mit benutzerdefinierten Fehlermeldungen durchführt. Ein separates JSON-Form-Schema JL7 (verwendet von fL7 / parseAgentFromJson) ist strenger, folgt aber einem anderen Code-Pfad (verwendet von --agents <json> und settings.agents).

#FeldTyp (Ig5 / DL7)ErforderlichStandardEnum / ConstraintKonf
1namestring, non-emptyjakeine — DL7: if(!T||typeof T!=="string")return nullC strings:308120, 309074
2descriptionstring, non-emptyjaJL7: .min(1, "Description cannot be empty")C strings:308120, 309074, 309076
3modelstringneinundefinedinherit (case-insensitive) normalisiert zu literal "inherit"; sonst pass-through getrimmtC strings:308120, 309075, 309076
4toolsstring|array (MDH union)neinundefinedeinzelner Token *undefined (bedeutet “alles erben”); duped via AXH/FbKC strings:308120 (MDH/AXH), 309075
5disallowedToolsstring|array (MDH)neinundefined”Wird ignoriert, wenn tools gesetzt ist” (laut Describe-Text); von Callern erzwungenC strings:308120, 309075
6effortstring|integerneinundefinedenum GN=["low","medium","high","xhigh","max"] ODER int; alias P37={med:"medium"}C strings:308120, 309075, GN/P37 inline
7permissionModestringneinundefinedenum $E = Gmq = [...kc] wobei kc=["acceptEdits","auto","bypassPermissions","default","dontAsk","plan"] (6 Werte)C strings:307649 (kc), 308120, 309075
8mcpServersz.unknown() (Ig5); array(jL7) (JL7)neinundefinedjedes Element: string ODER record(string, MCPServerSpec); safeParse pro Element in DL7C strings:308120, 309075, 309076
9hooksz.unknown() (Ig5); _u() (JL7)neinundefinedlazy zur Laufzeit validiert via TKO_u().safeParse (settings.json Hooks-Form)C strings:308120, 309073 (TKO), 309076
10maxTurnsunion(number, string, null)neinundefinedpositive integer (geparst von W46 — akzeptiert numerisch oder numerischen String)C strings:308120, 309075 (W46), 309076
11skillsstring|array (MDH)nein[] (ausgegeben)normalisiert via ml(q.skills) = FbK(H) ?? []; kein * Wildcard (im Gegensatz zu tools)C strings:308120, 309075
12initialPromptstringneinundefinednur Whitespace → undefined; nur automatisch übermittelt, wenn der Agent die Main Session ist (via --agent / settings), als Subagent ignoriertC strings:308120, 309075
13memorystringneinundefinedenum ["user","project","local"]C strings:308120, 309075, 309076
14backgroundstring|bool (eiH=EL8)neinundefinedakzeptiert true / false / "true" / "false"; nur truthy wird zu true normalisiert, sonst undefinedC strings:308120, 309075
15isolationstringneinundefinedenum nur ["worktree"] (NICHT ["none","worktree"] — das ist ein anderes Schema bei strings:313284 für Background-Session-Settings)C strings:308120, 309075, 309076

Subtile Beobachtung, die der Widerlegung standgehalten hat: Obwohl skills “optional” ist, lautet die Emit-Klausel von DL7 ...I !== void 0 && {skills: I} und ml(undefined) gibt [] zurück (nicht undefined), sodass der endgültig ausgegebene Record skills: [] enthält, selbst wenn das Frontmatter das Feld weglässt. Dies beeinflusst nachgelagerte Gleichheitsprüfungen — als Flag für den qwen-code Port vormerken.

Mögliche zusätzliche Felder über die 15 hinaus

#FeldTypStandardEnum / ConstraintKonf
16colorstringundefinedenum _Y = ["red","blue","green","yellow","purple","orange","pink","cyan"]; beschrieben als "@internal — display color in the agents UI"; Werte außerhalb von _Y werden zur Parse-Zeit stillschweigend verworfen (DL7 emittiert ...z && typeof z === "string" && _Y.includes(z) && {color: z})C strings:308120, 309075, _Y inline
Dies ist das einzige neue Agent-Frontmatter-Feld über die Liste von #4821 hinaus. Felder, die durchsucht, aber NICHT auf Ig5 / JL7 gefunden wurden: version, tags, labels, category, icon, alias / aliases, experimental, deprecated, owner, author, homepage, displayName, shortDescription (diese tauchten nur im Skill-Schema bg5 oder bei nicht verwandten Identifikatoren auf).

Loader — Datei- und Funktionszuordnung

BereichFunktionOrtConf
Top-Level-Registry-AssemblerQL (Exportname getAgentDefinitionsWithOverrides)strings:309076C
Filesystem-Walker (gemeinsam genutzt mit Skills/Commands/Output-Styles)Gm (memoisiert über h6)strings:312887C
Pro-.md-Erkennungd_q (= loadMarkdownFiles, ripgrep mit --files --hidden --follow --no-ignore --glob *.md, 3 s AbortSignal.timeout, Fallback wY3 wenn __("true"))strings:312887C
Pro-Datei-Parser (Markdown)DL7 (= parseAgentFromMarkdown)strings:309074C
Pro-Datei-Parser (JSON)fL7 (= parseAgentFromJson), verwendet JL7-Schemastrings:309073C
Plugin-Agent-Loaderb0_ → pro-Verzeichnis oR7 → pro-Datei sR7strings:308780, 308779C
Built-insnaH() — gibt [JqH=general-purpose, KL7=statusline-setup, …] sowie implizit YI=fork ausstrings:309073, 308663C
Override-ResolverDS() (= getActiveAgentsFromList) — siehe Resolution Orderstrings:309073C
Cache-Invalidationu0_() (= clearAgentDefinitionsCache) — leert QL.cache + Gm.cachestrings:309073C
FS-Watcher (chokidar)s_T()Q4_=s_T() bei Modul-Initialisierung (WB6)strings:316417C

Gm("agents", _) liest drei baseDirs (policySettings, userSettings, projectSettings), die jeweils im Record getaggt werden, und entfernt dann Duplikate nach Inode (verwirft Duplikate mit gleichem Inode von Symlinks/Hardlinks, loggt Skipping duplicate file '<path>' from <source> (same inode already loaded from <firstSource>)). Telemetrie: tengu_dir_search mit managedFilesFound, userFilesFound, projectFilesFound, projectDirsSearched, subdir.

Reihenfolge der Auflösung — definitive Priorität

Die Funktion DS() filtert ihre Eingabe nach source und iteriert dann ein Array fester Reihenfolge in eine Map, die nach agentType keyt. Da Map.set überschreibt, gewinnt der LETZTE berührte Bucket:

[built-in, plugin, userSettings, projectSettings, flagSettings, policySettings] ^ höchste Priorität
QuelleUrsprungOverride-PrioritätConf
built-innaH() (hardcoded in der Binärdatei)1 (niedrigste)C strings:309073
pluginb0_ → pro-Plugin agentsPath/agentsPaths2C strings:308780
userSettings~/.claude/agents/ (CLAUDE_CONFIG_DIR oder ~/.claude)3C strings:312887, 307489
projectSettings<cwd>/.claude/agents/ PLUS iV_() Walk bis zum Homedir/Git-Root4C strings:312887, iV_ inline
flagSettings--agents <json> CLI-Flag (Schema qKO = h.record(h.string(), JL7()))5C strings:330190, 309076
policySettingssystemverwaltetes Verzeichnis: macOS /Library/Application Support/ClaudeCode/.claude/agents, Linux /etc/claude-code/.claude/agents, Windows C:\Program Files\ClaudeCode\.claude\agents6 (höchste)C strings:307649 (H2), 312887

Kollisionen werden still aufgelöst – es wird nur das Telemetrie-Event tengu_plugin_name_collision ausgelöst (winner_source: T.at(-1)); dem Benutzer wird keine Warnung der Art “X überschreibt built-in” angezeigt. (strings:308742 hMH.)

Subtiles Verhalten: iV_() läuft innermost-first (innerste zuerst) von cwd nach oben ab, aber da bei Map.set der letzte gewinnt, gewinnt das outer-tree .claude/agents/ gegenüber dem inner-tree innerhalb von projectSettings. Dies ist überraschend – in den offenen Fragen vermerken.

Frontmatter-Parser

FrageAntwortConf
Verwendete Bibliothek?Keine – handgeschriebener Splitter lz, der Bun.YAML.parse aufruft (über Wrapper l5H). Kein gray-matter, js-yaml oder front-matter in der Binärdatei.C strings:307902 (l5H), 307905 (lz), 110303 (Bun.YAML errors)
Regexn5H = /^---\s*\n([\s\S]*?)---\s*\n?/C strings:307905
FehlerbehandlungYAML-Parse-Fehler → Retry mit Tab-zu-2-Space-Normalisierung; wenn es immer noch fehlschlägt, Failed to parse YAML frontmatter in <file>: <err> auf Warnstufe loggen und {frontmatter: {}, content: body} zurückgeben (wirft NIEMALS einen Fehler)C strings:307905, 151839
Body-ExtraktionEinfacher String-Slice H.slice(K[0].length) nach dem schließenden ---; später normalisiert durch v$H (wahrscheinlich Entfernen führender Newlines)C strings:307905
Geteilt zwischen Agents / Skills / Commands / Output-Styles?Ja – dasselbe lz wird von Iq_ (Skill-Loader), f13 (Deprecated-Commands-Loader) und dem Agent-Loader über Gmd_q wiederverwendetC strings:312690
Schema-ValidatorZod v4 (gebundelt). v4-exklusive Marker looseObject, treeifyError, prettifyError, toJSONSchema vorhandenC strings:141270-141395, 141586
ValidierungsmodusShadowahH("agent", frontmatter) führt ug5.agent().strict().safeParse() nur für Telemetrie aus; DL7 ignoriert das Ergebnis und fährt mit seiner eigenen Pro-Feld-Validierung fort. Das nachsichtige Frontmatter-Objekt ist die Source of Truth zur Laufzeit.C strings:308120 (ahH/ug5), 309074 (DL7 calls but ignores)
Telemetrie-Eventstengu_frontmatter_shadow_unknown_key, tengu_frontmatter_shadow_mismatch (dedupliziert über prozessinternes Set A37)C strings:154634, 154636

Wiring — Agent-Tool + CLI-Flag

EbeneBeschreibungConf
Task/Agent-Tool-Schema ($_3)Deklariert subagent_type: string.optional(); wenn weggelassen, Fallback auf general-purpose (oder fork, wenn AI() true zurückgibt)C strings:~309220
Subagent-LookupactiveAgents.find(a => a.agentType === requestedType) gegen toolUseContext.options.agentDefinitions.activeAgentsC strings:~309220
Fuzzy-FallbackMWK(s) = s.normalize("NFKC").toLowerCase().replace(/[\p{White_Space}\p{Pd}_]+/gu, ""); mehrdeutiger Match → AgentTypeError; sauberer Rematch → tengu_subagent_type_normalizedC strings:~309220
Permission-GatelV_(toolPermissionContext, "Task", agentType) – Ablehnung → Agent type '<x>' has been denied by permission rule 'Task(<x>)' from <source>.C strings:~309220
System-Prompt-QuelleMarkdown-Body wird zu getSystemPrompt: () => body + ('\n\n' + UVH(agentType, memoryScope) when memory enabled) – Closure wird zur Parse-Zeit erfasstC strings:309074-6 (DL7)
Main-Thread-RenderPp({mainThreadAgentDefinition, …}) – wenn der Agent appendSystemPrompt: true hat (das Catch-All claude built-in), wird der Body an den Default angehängt; andernfalls ERSETZT er den DefaultC strings:311015
--agent <name> CLIDeklariert über Commander; Action-Handler if(I) process.env.CLAUDE_CODE_AGENT = I; – schreibt in Env-Var, woanders in appState.agent gelesen. Wird auch in der PID-Datei aufgezeichnet.C strings:330190, 142138
--agents <json> CLISeparates Flag; JSON-Record {name: {description, prompt, …}} validiert durch qKO = h.record(h.string(), JL7()); tritt derselben activeAgents-Registry mit source: flagSettings beiC strings:330190, 309076

Lifecycle — Cold Load + Hot Reload

AspektVerhaltenConf
Cold LoadLazy — QL wird über h6 memoisiert (Cache-Wrapper); der erste Zugriff liest Dateisystem + Plugins, nachfolgende Zugriffe geben den Cache zurückC strings:309076
Hot-Reload-Mechanismuschokidar watcher s_T() bei Modul-Initialisierung registriert (WB6); überwacht .claude/agents (User + Projekt) sowie Skills- und Commands-VerzeichnisseC strings:316417
Watcher-Flagspersistent:true, ignoreInitial:true, depth:2, awaitWriteFinish:{stabilityThreshold,pollInterval}, ignored:(p,s) => s?.isFile() ? !p.endsWith(".md") : false, usePolling:kZ4 (macOS true), Events add/change/unlinkC strings:316417
Debounce300 ms (l_T = 300); Handler ruft RIH(), Vv(), u0_(), … auf — u0_() invalidiert den Agent-CacheC strings:316417, 309073
Adaptives Pollingaktiv = n_T = 2000 ms Intervall; idle (keine Interaktion für r_T = 60000 ms) → i_T = 30000 ms; erstellt die chokidar-Instanz beim Wechsel neuC strings:316417
/agents Slash-Commandlocal-jsx UI zum Verwalten von Agents (Library/create/edit/delete/run) — KEIN Rescan-CommandC strings:314593
/reload-plugins Slash-CommandFührt QL(W8()) erneut aus, zählt Agents neu; deckt Agents aus Plugins ab (die chokidar NICHT überwacht)C strings:314595, 190948
Andere InvalidierungspfadeclearSessionCaches (verwendet von /clear) ruft ebenfalls u0_() aufC strings:313246

Offene Fragen (Phase 1)

#FrageConfLösungsweg
Q1Ist das Weglassen von color in #4821 beabsichtigt (es ist @internal) oder ein Versehen?OAls beabsichtigt behandeln — das Feld portieren, aber als internal/UI-only markieren
Q2Ist das nachsichtige Verhalten von DL7 (background akzeptiert Strings, maxTurns akzeptiert Strings) ein dokumentiertes, benutzerseitiges Feature oder ein Hack für Abwärtskompatibilität?OFür Parität spiegeln, aber in den Port-Docs warnen
Q3Warum ist die isolation-Enum ["worktree"] nur für Agents, während das Schema für background-session-Einstellungen ["none","worktree"] akzeptiert?OWahrscheinlich bedeutet “no isolation” = weggelassenes Feld; explizit dokumentieren
Q4Sitzt --agents <json> (flagSettings) absichtlich auf Precedence 5 (über Projekt, unter Policy)?Oqwen-code kann das Flag in v1 überspringen und die Entscheidung vertagen
Q5Innermost-first Push durch iV_ + Map.set last-wins → outer-tree wins bei projectSettings-Kollisionen. Fußfalle oder beabsichtigt?Oqwen-code sollte innermost-wins-Semantik wählen, um die Fußfalle zu vermeiden

Phase 2 — Implementierungsplan für qwen-code

Aktueller Stand — Überblick in einem Absatz

qwen-code liefert bereits eine umfangreiche Subagent-Infrastruktur aus: SubagentManager (packages/core/src/subagents/subagent-manager.ts) implementiert CRUD für Markdown+YAML-Frontmatter-Dateien in .qwen/agents/ (Projekt) und ~/.qwen/agents/ (User), unterstützt durch einen eigenen YAML-Parser (packages/core/src/utils/yaml-parser.ts — keine gray-matter / yaml-Dependency, bestätigt durch package.json). SubagentConfig (packages/core/src/subagents/types.ts:41-122) verfügt bereits über name, description, tools, disallowedTools, approvalMode, systemPrompt, model, runConfig, color, background. SubagentLevel unterstützt bereits fünf Scopes (session, project, user, extension, builtin) mit der Precedence session > project > user > extension > builtin (subagent-manager.ts:189-220). Das Agent-Tool (packages/core/src/tools/agent/agent.ts) deklariert subagent_type und aktualisiert seine Schema-Enum dynamisch über subagentManager.changeListener. Eine convertClaudeAgentConfig()-Bridge existiert bereits in packages/core/src/extension/claude-converter.ts:162-220 mit einem Tool-Namen-Mapping und einem permissionMode → approvalMode-Mapping. Die Lücke ist: (a) dem Schema fehlen 8 Felder aus #4821 (effort, permissionMode als First-Class, mcpServers, hooks, maxTurns als Top-Level, skills, initialPrompt, memory, isolation); (b) kein --agent <name> CLI-Flag; (c) kein chokidar-Style Hot Reload (Extension-Style Invalidierung existiert, aber nicht für Filesystem-Agents); (d) maxTurns ist derzeit unter runConfig.max_turns verschachtelt — muss gemäß #2409 auf Top-Level gehoben werden.

Architekturentscheidungen

D1. Bestehenden YAML-Parser für Frontmatter wiederverwenden

Entscheidung: packages/core/src/utils/yaml-parser.ts wiederverwenden (bereits verwendet von SubagentManager.parseSubagentContent und dem Skill-Loader). Begründung: Claude Codes lz ist derselbe gemeinsam genutzte Parser, der für Skills + Commands + Agents verwendet wird; qwen-code spiegelt dieses Muster bereits wider. Das Hinzufügen von gray-matter oder js-yaml ist unnötiger Aufwand. Der bestehende Parser verarbeitet das --- … ----Splitting und ist bei fehlerhaftem Input still (entspricht der warn-and-return-empty-Haltung von lz).

D2. Resolution / Precedence-Reihenfolge

Entscheidung: session > project (.qwen/agents/) > user (~/.qwen/agents/) > extension > builtin verwenden — d. h. die bestehende qwen-code SubagentLevel-Reihenfolge beibehalten und Claude Codes flagSettings/policySettings-Buckets in v1 NICHT spiegeln. Begründung: Claude Codes policySettings (Managed Dir) ist eine Enterprise-Deploy-Story, die qwen-code nicht hat. Flag-injizierte Agents (--agents <json>) sind ein Power-User-Feature, das in P4 landen kann. Die bestehende Fünf-Ebenen-Precedence von qwen-code deckt bereits die Fälle ab, die #4821 betreffen: Projekt überschreibt User überschreibt Built-in. Die extension-Ebene fügt sich sauber zwischen User und builtin ein.

D3. Validierung — bestehenden SubagentValidator beibehalten

Entscheidung: SubagentValidator (packages/core/src/subagents/) erweitern, um die acht neuen Felder zu validieren. Zod NICHT einführen, es sei denn, die Pipeline von skillManager verwendet es bereits; wenn der bestehende Validator handgerollt ist, bleibt er handgerollt. Begründung: Claude Codes Ig5 ist nur Shadow — die Runtime-Validierung ist handgerolltes DL7. Das Anpassen an dieses Muster hält Fehlermeldungen lesbar (z. B. Agent file <path> has invalid permissionMode '<x>'. Valid options: …) ohne eine weitere Dependency hereinzuziehen. Wenn skillManager bereits zod verwendet, folge dieser Wahl aus Konsistenzgründen — TBD durch Lesen des Skill-Codes in der P1-Vorbereitung.

D4. Hot Reload — vertagen; auf Cold Load + explizites Reload setzen

Entscheidung: v1 liefert KEINEN chokidar-Watcher aus. Cache-Invalidierungs-Hooks existieren bereits (subagentManager hat changeListener und expliziten CRUD-gesteuerten Refresh). Reload auf Projekt-Ebene erfolgt beim Session-Start; In-Session-Edits über die /agents-UI invalidieren. Ein /reload-agents-Slash-Command (oder als Piggyback auf /reload-plugins) kann in P4 landen, wenn Nutzerbedarf besteht. Begründung: Hot Reload über FS-Watcher ist teuer (chokidar fügt eine Polling-Schleife mit adaptivem Scheduling hinzu — allein Claude Codes Implementierung umfasst ~150 Zeilen Bookkeeping). Cold-Load-on-Startup reicht für v1 völlig aus und entspricht der heutigen Verdrahtung von SubagentManager. Tür für P4 öffnen.

D5. --agent <name> CLI-Flag anbinden — v1 im Scope

Entscheidung: --agent <name> zu packages/cli/src/config/config.ts CliArgs hinzufügen. Verhalten: Gegen die aufgelöste Registry nachschlagen, den Agent als Main-Thread-Agent setzen, bei Nicht-Auflösung des Namens einen klaren Fehler werfen. Claude-Code-Semantik beachten (Standard-System-Prompt ersetzen, es sei denn, der Agent hat appendSystemPrompt: true). KEINE CLAUDE_CODE_AGENT-Env-Var-Indirektion verwenden — das Config-Objekt von qwen-code kann es direkt tragen. Begründung: Dies ist das benutzerseitige Handle für #4821 — ohne sie sind deklarative Agents nur über den subagent_type-Param des Agent-Tools erreichbar, was für einen “set my default agent”-Use-Case zu indirekt ist. --agents <json> (Plural) kann auf P4 vertagt werden.

D6. Workflow.agentType-Koordination — Interface-Vertrag

Entscheidung: Ein stabiles Resolver-Interface bereitstellen, das createProductionDispatch aus PR #4732 beim Mergen aufrufen kann. Im Detail:

VertragOwner
Frontmatter-name IST der Workflow-agentType-String (Key-Gleichheit, case-sensitive)dieser PR
Der hartcodierte disallowedTools-Floor des Workflows ([SEND_MESSAGE, EXIT_PLAN_MODE], gespiegelt von Upstream Tg8; in PR #4732 verifiziert als ToolNames.SEND_MESSAGE, ToolNames.EXIT_PLAN_MODE) wird mit disallowedTools auf Agent-Ebene UNION-verknüpft — der Floor wird immer angewendet, auch wenn die Agent-Definition tools setztWorkflow-PR konsumiert
opts.isolation pro Aufruf überschreibt den Standard isolation: 'worktree' pro AgentWorkflow-PR konsumiert
model, effort, permissionMode, maxTurns aus der Agent-Definition überschreiben bei gesetztem Wert die Workflow-StandardsWorkflow-PR konsumiert
Agent-Body wird zum systemPrompt des Subagents; WORKFLOW_SUBAGENT_SYSTEM_PROMPT des Workflows ist der Fallback, wenn agentType nicht aufgelöst wirdWorkflow-PR konsumiert
Wenn agentType nicht gesetzt ist oder nicht aufgelöst werden kann, fällt der Workflow auf den Built-in-Workflow-Subagent zurück (graceful, kein Throw)Workflow-PR konsumiert
Auflösung des Widerspruchs zwischen #4721 / #4821 (tools vs. disallowedTools-Präzedenz): Dieser Port gestaltet die Agent-Registry so, dass disallowedTools immer separat von tools geführt werden. Die Regel “ignoriert, wenn tools gesetzt ist” aus der Tabelle von #4821 wird von den Agent-Tool-Callern durchgesetzt (d. h. beim Erstellen der ToolConfig des Subagenten), nicht zur Parse-Zeit. Dadurch kann der Workflow seinen Floor immer unabhängig davon, ob der Agent tools setzt, mit disallowedTools vereinen. Die Agent-Registry ist ein passiver Datenträger; die Präzedenzregeln liegen an der Dispatch-Stelle. Dies löst den scheinbaren Konflikt zwischen der “ignoriert”-Regel von #4821 und der “union”-Regel von #4721.

Kanonisierung der Tool-Namen: Verwende ToolNames.SEND_MESSAGE und ToolNames.EXIT_PLAN_MODE (verifiziert gegen den PR #4732 Diff), exportiert als benannte Konstanten aus packages/core/src/agents/runtime/workflow-orchestrator.ts, sobald dieser landet. Der Declarative-Agents-Port selbst muss diese NICHT importieren – sie sind der Floor des Workflows, der an der Workflow-Dispatch-Stelle angewendet wird.

Modul-Layout

PfadNeu / GeändertZweck
packages/core/src/subagents/types.tsGeändert8 neue Felder zu SubagentConfig hinzufügen: effort, permissionMode (mappt bereits via approvalMode – beide behalten? siehe D7 unten), mcpServers, hooks, maxTurns (auf Top-Level befördern, runConfig.max_turns als deprecated markieren), skills, initialPrompt, memory, isolation
packages/core/src/subagents/subagent-manager.tsGeändertparseSubagentContent / serializeSubagent erweitern, um neue Felder round-trip-fähig zu machen; SubagentValidator-Aufrufe erweitern
packages/core/src/subagents/subagent-validator.ts (vermuteter Pfad)GeändertFeld-spezifische Validierung hinzufügen, passend zu den Fehlermeldungen von DL7: Agent file <path> has invalid permissionMode '<x>'. Valid options: … usw.
packages/core/src/subagents/agent-frontmatter-schema.tsNeuSingle Source of Truth für Enum-Konstanten: EFFORT_VALUES, PERMISSION_MODE_VALUES, MEMORY_VALUES, ISOLATION_VALUES, COLOR_VALUES. Claude Code 2.1.168 wortgetreu spiegeln.
packages/core/src/subagents/builtin-agents.tsGeändertNeue Felder sind standardmäßig undefined; keine Verhaltensänderung
packages/core/src/tools/agent/agent.tsGeändertNeue Felder aus der aufgelösten SubagentConfig lesen, wenn Subagent-Optionen konstruiert werden (model, maxTurns, permissionMode, effort); isolation-Per-Call-Override-Semantik für #4721 durchreichen
packages/cli/src/config/config.tsGeändert--agent <name>-Flag hinzufügen; beim Start gegen SubagentManager auflösen; Fehler werfen, wenn der Name nicht aufgelöst werden kann
packages/cli/src/config/config.test.tsGeändertTests für --agent-Flag-Auflösung + Fehlerpfad
packages/core/src/extension/claude-converter.tsGeändertMapping für neue Felder beim Importieren von Claude .md-Dateien hinzufügen (mcpServers, hooks, maxTurns auf Top-Level, memory, isolation usw.)
packages/core/src/subagents/agent-frontmatter-schema.test.tsNeuSnapshot-Tests für Enum-Listen; Round-Trip-Parse/Serialise-Tests
packages/core/src/subagents/subagent-manager.test.tsGeändertTests für Validierung neuer Felder, Präzedenz, Fehlermeldungen
packages/core/src/tools/agent/agent.test.tsGeändertTests für das Durchreichen neuer Felder in die Subagent-Laufzeit
docs/cli/agents.md (falls vorhanden) oder docs/declarative-agents.mdNeuBenutzerreferenz: 16-Felder-Schema + Beispiele

D7. permissionMode vs approvalMode — überbrücken, nicht ersetzen

Entscheidung: Akzeptiere BEIDE, permissionMode (Claude-kompatibel) und das bestehende approvalMode (qwen-kompatibel), im Frontmatter. Beim Parsen, wenn permissionMode gesetzt ist, mappe es auf approvalMode unter Verwendung der bestehenden Tabelle in claude-converter.ts:195-208 (default → default, plan → plan, acceptEdits → auto-edit, dontAsk → default, bypassPermissions → yolo). Wenn beide vorhanden sind, gewinnt approvalMode (spezifischer für qwen-code) und es wird ein Telemetrie-Event im Stil von tengu_frontmatter_shadow_* ausgelöst, das vermerkt, dass beide gesetzt wurden. Begründung: Erhält die Abwärtskompatibilität mit bestehenden .qwen/agents/*.md, die approvalMode verwenden, während permissionMode von Claude Code wortgetreu akzeptiert wird, sodass Benutzer Claude-Code-Agent-Dateien unverändert übernehmen können.

Schema-Mapping-Tabelle

Claude Code 2.1.168 Feldqwen-code FeldAnpassungHinweise
namenamekeineidentisch, erforderlich
descriptiondescriptionkeineidentisch, erforderlich
modelmodelakzeptiere inherit, fast, haiku, sonnet, opus oder authType:model-idqwen-code unterstützt bereits das breitere Vokabular; inherit ist neu
toolstoolsakzeptiere string|array; * → undefined (inherit-all)bereits als Array unterstützt; String- + *-Handling hinzufügen
disallowedToolsdisallowedToolsakzeptiere string|array; immer separat von tools geführtPräzedenzregel (#4821 “ignoriert, wenn tools gesetzt ist”) wird von Callern durchgesetzt, nicht vom Parser
efforteffort (neu)Enum low/medium/high/xhigh/max + Integer; Alias med → mediumLaufzeiteffekt ist qwen-spezifisch (auf bestehenden Thinking-Effort-Knopf mappen, falls vorhanden, sonst speichern und ignorieren)
permissionModepermissionMode (neu) + brückt zu approvalModeEnum acceptEdits/auto/bypassPermissions/default/dontAsk/plan; Mapping-Tabelle gemäß D7Claude-Format wortgetreu akzeptieren
mcpServersmcpServers (neu)Array aus (string | {name: spec}); pro Element validieren, fehlerhafte Einträge mit Warnung verwerfenAnbindung an MCP-Laufzeit in P4
hookshooks (neu)Objekt, das der hooks-Form von settings.json entsprichtAnbindung an Hook-Laufzeit in P4
maxTurnsmaxTurns (neu auf Top-Level)positive Ganzzahl; akzeptiere numerischen String für Paritätvon runConfig.max_turns auf Top-Level befördert; verschachtelte Form als deprecated Alias beibehalten
skillsskills (neu)Array aus Skill-Namen; Komma-separierter String wird ebenfalls akzeptiertLaufzeit: über skillManager vorladen, wenn Agent startet
initialPromptinitialPrompt (neu)String; nur Whitespace → undefined; wird nur ausgelöst, wenn Agent die Hauptsitzung istangebunden über den --agent-Flag-Pfad
memorymemory (neu)Enum user/project/local; lädt aus .qwen/agent-memory/<name>/ usw.Laufzeit in P4
backgroundbackgroundakzeptiere bool oder String "true"/"false"; nur truthy → truebereits unterstützt; Parse-Regeln auflockern
isolationisolation (neu)Enum nur ["worktree"]Laufzeit gehört zum Workflow-PR (#4732 P3+); Registry führt nur das Feld
color (undokumentiert #16)colorEnum _Y = ["red","blue","green","yellow","purple","orange","pink","cyan"]; Werte außerhalb werden stillschweigend verworfenbereits in qwen SubagentConfig; Validierung straffen, um sie der Claude-Code-Allowlist anzupassen

TDD-Testplan

AbschnittTestdateiWas geprüft wird
Schema-Enum-Konstantenagent-frontmatter-schema.test.ts (neu)EFFORT_VALUES, PERMISSION_MODE_VALUES, MEMORY_VALUES, ISOLATION_VALUES, COLOR_VALUES stimmen Byte für Byte mit Claude Code 2.1.168 überein (Snapshot)
Parser — Happy Pathsubagent-manager.test.tsRound-Trip-Parsing von .qwen/agents/test.md mit allen 16 Feldern → ausgegebener Record hat die erwartete Struktur
Parser — Pflichtfeldersubagent-manager.test.tsFehlendes name gibt null + Warn-Log zurück; fehlendes description gibt null + Warn-Log zurück
Parser — Enum-Validierungsubagent-manager.test.tsFalsche permissionMode / memory / isolation / effort / color geben jeweils eine spezifische Warnung aus (passend zur DL7-Formulierung) und das Feld wird verworfen
Parser — tolerante Feldtypensubagent-manager.test.tsbackground: "true"true; maxTurns: "5"5; effort: "med""medium"; tools: "Read,Edit"["Read","Edit"]; tools: "*"undefined
Parser — Color-Allowlistsubagent-manager.test.tscolor: "magenta" wird stillschweigend verworfen (kein Fehler), color: "blue" wird beibehalten
Besonderheit des skills-Feldessubagent-manager.test.tsDas Weglassen von skills führt zu skills: [] (entspricht dem Emit-Verhalten von Claude Code DL7)
Präzedenz bei der Auflösungsubagent-manager.test.tsGleicher name in Projekt + User → Projekt gewinnt; in User + Builtin → User gewinnt; in Extension + Builtin → Extension gewinnt
Inode-Deduplizierungsubagent-manager.test.tsZwei Pfade zum selben Inode (Symlink) → nur ein Record, Log wird ausgegeben
permissionMode-Bridgesubagent-manager.test.tspermissionMode: bypassPermissions → aufgelöstes approvalMode: yolo; beide gesetzt → approvalMode gewinnt + Telemetrie
--agent CLI-Flagpackages/cli/src/config/config.test.tsFlag setzt den Main-Thread-Agent; nicht aufgelöster Name wirft Fehler mit Agent type '<x>' not found. Available agents: …
Agent-Tool Fuzzy-Fallbackagent.test.tssubagent_type: "Test_Engineer" wird über NFKC-Lowercase-Normalisierung zu einem registrierten test-engineer aufgelöst
Agent-Tool Not-Found-Fehleragent.test.tsNicht aufgelöster subagent_type → Fehlermeldung passt zu Agent type '<x>' not found. Available agents: <list>
Workflow-Contractagent-frontmatter-schema.test.tsExportierte getAgentByName(name)-Schnittstelle gibt die vollständige SubagentConfig zurück, einschließlich isolation, disallowedTools, model, effort, permissionMode, maxTurns (verbrauchbar durch Workflow PR #4732)

Phasenweiser PR-Plan

PhaseTitelScopeBlockiert
P1feat(core): declarative agent schema fields (effort, permissionMode, maxTurns top-level, memory, isolation, color allowlist)Felder zu SubagentConfig hinzufügen; Parser + Validator + Serializer erweitern; runConfig.max_turns als deprecated markieren; Enum-Konstanten-Modul hinzufügen; TestsKeine
P2feat(core): wire new agent fields into Agent tool runtimemodel, effort, maxTurns, permissionMode/approvalMode-Bridge in AgentTool.execute()AgentHeadless.create() Call-Site durchschleusen; TestsP1
P3feat(cli): --agent flag for main-thread agent selection--agent <name> zu CliArgs hinzufügen; beim Start auflösen; Fehlerpfad; TestsP1
P4(optional, scope-creep) feat(core): mcpServers + hooks + skills + initialPrompt + memory runtimeDie vier “in v1 nur Metadaten”-Felder mit tatsächlichen Runtime-Effekten verdrahtenP1, plus Skill/MCP/Hook-Subsysteme

Jeder PR zielt auf ≤ 800 LOC Delta (ohne Tests); P1 ist mit ~600 LOC für Validator + Tests der größte.


Phase 3 — Koordinationsmatrix mit Workflow-Port (#4721 / PR #4732)

Declarative-Agents-FeatureWorkflow-InteraktionOwnerBlockiert durch
name-Feld als Registry-Keyopts.agentType Lookup-String des Workflows (#4721  explizit)dieser PR definiert den Registry-Contract; Workflow-PR konsumiert ihnkeine — Registry-Form kann sich zuerst stabilisieren
disallowedTools-Feld beim AgentWorkflow bildet UNION mit hartcodiertem Floor [SEND_MESSAGE, EXIT_PLAN_MODE] (gemäß #4721  §2 — verifiziert gegen PR #4732 Diff: ToolNames.SEND_MESSAGE, ToolNames.EXIT_PLAN_MODE)dieser PR enthält das Feld; Workflow-PR bildet UNION beim DispatchWorkflow PR #4732 P3 landet
tools-Feld beim AgentWorkflow reicht es unverändert an ToolConfig.tools des Subagents weiterdieser PR enthält das Feld; Workflow-PR schleust es durchWorkflow PR #4732 P3
model-Feld beim Agentopts.model des Workflows überschreibt pro Aufruf; model des Agents ist der Defaultdieser PR enthält das Feld; Workflow-PR löst die Präzedenz aufWorkflow PR #4732 P3
effort-Feld beim AgentCall-Site-Override des Workflows gewinnt; Agent-Default als Fallbackdieser PR enthält das Feld; Workflow-PR löst aufWorkflow PR #4732 P3
permissionMode-Feld beim AgentWird beim Dispatch auf approvalMode des Subagents gemappt; Call-Site-Override des Workflows gewinntdieser PR enthält das Feld via D7-Bridge; Workflow-PR schleust es durchWorkflow PR #4732 P3
maxTurns-Feld beim AgentErsetzt hartcodiertes WORKFLOW_SUBAGENT_MAX_TURNS = 50 des Workflows, wenn der Agent es setztdieser PR enthält das Feld; Workflow-PR löst die Präzedenz aufWorkflow PR #4732 P3
isolation: 'worktree'-Feld beim AgentDefault; opts.isolation pro Aufruf überschreibt (#4721  §3)dieser PR enthält das Feld; Workflow-PR besitzt die RuntimeWorkflow PR #4732 P3+ (wirft derzeit in P1 einen Fehler)
initialPrompt-Feld beim AgentWorkflow nutzt es nicht (wird nur ausgelöst, wenn der Agent via --agent die Main-Session ist)dieser PR + CLIkeine (unabhängig)
memory, mcpServers, hooks, skillsWorkflow hat keine spezielle Behandlung, außer es an die Subagent-Runtime weiterzureichendieser PR enthält die Felder; Runtime-Verdrahtung in P4 / zukünftigzukünftige PRs
EXCLUDED_TOOLS_FOR_SUBAGENTS-UpdatesWorkflow PR #4732 fügt WORKFLOW zum Set hinzu (basierend auf der Issue/PR-Kontext-Analyse — auch wenn die kritische Prüfung angemerkt hat, dass dies noch NICHT in agent-core.ts auf main ist, sondern nur im Worktree)Workflow-PR ist verantwortlich; dieser PR bleibt unberührtkeine
Tool-Name-Kanonical-Form für Workflow-Floor (ToolNames.SEND_MESSAGE)Dieser PR importiert nicht die Floor-Konstanten; er enthält nur die disallowedTools-Strings wie geschrieben. Der Workflow-PR ist für die Kanonisierung verantwortlich.Workflow-PRWorkflow PR #4732
Release-ReihenfolgeDieser PR (P1+P2+P3) wird unabhängig vom Workflow released. Workflow PR #4732 P3 ist davon abhängig, dass der getAgentByName()-ähnliche Resolver dieses PRs importierbar ist.parallel bis P3 des WorkflowsWorkflow P3 liest aus den Exports dieses PRs
Kein zirkulärer Block: Dieser PR und der Workflow-PR können parallel durch ihre P1/P2-Phasen gemerged werden. Sie synchronisieren sich bei workflow-P3, welches den Registry-Resolver dieses PRs benötigt. Wenn dieser PR zuerst gemerged wird, liest workflow-P3 daraus. Wenn der Workflow-PR zuerst gemerged wird, wird er mit dem bestehenden subagent_type-Lookup ausgeliefert (das bei einem Miss die Workflow-Standardwerte zurückgibt) und wechselt zu dem umfangreicheren Resolver, sobald dieser PR gemerged wird.

Phase 4 — Risiken und offene Fragen

Risiken

#RiskMitigation
R1Schema-Drift zwischen Claude Code Minor Releases (2.1.168 → 2.1.x)Das Enum-Constants-Modul mit einem Doc-Kommentar auf “verified against 2.1.168” pinnen; den Strings-Grep bei neuen Releases als Teil des feature-reverse-Skills erneut ausführen
R2runConfig.max_turns → Top-Level maxTurns ist eine Breaking Schemaänderung für bestehende .qwen/agents/*.md-DateienVerschachtelte Form als deprecated Alias mit einem Deprecation-Zyklus beibehalten; Warnung beim Parsen ausgeben, im CHANGELOG dokumentieren
R3permissionModeapprovalMode Roundtrip ist verlustbehaftet (Claude hat 6 Modi, Qwen hat ca. 4)Beide Richtungen explizit gemäß D7 mappen; Telemetrie bei Dual-Set ausgeben; NICHT stillschweigend beim Speichern umschreiben
R4Neue Felder (hooks, mcpServers, skills, memory) werden in der Registry mitgeführt, haben aber in v1 keine Runtime → Benutzer könnten sie setzen und erhalten stillschweigend keinen Effektv1-Scope klar dokumentieren; einmalig einen Info-Log pro Agent ausgeben, wenn ein “mitgeführtes, aber noch nicht in der Runtime verfügbares” Feld nicht leer ist
R5Adversarial-Verify hat markiert, dass EXCLUDED_TOOLS_FOR_SUBAGENTS auf main NICHT WORKFLOW enthält – das könnte bedeuten, dass der Workflow-Port noch nicht gemerged ist oder dass der Recursive-Fanout-Guard fehltMit dem Autor des Workflow-PRs (LaZzyMan = ich) bestätigen, dass der Guard mit PR #4732 gemerged wird, nicht in diesem Port
R6Das Outer-tree-beats-inner-tree-Verhalten von projectSettings (Q5) ist eine Stolperfalle, wenn es gespiegelt wirdqwen-code wählt explizit innermost-wins; getestet über R5-Fixture
R7Das Feld color ist im Describe-Text der Binärdatei als @internal dokumentiert – wir porten möglicherweise etwas, das Anthropic explizit nicht unterstütztPorten, aber auch in den qwen-code-Docs als @internal markieren; als UI-only behandeln; nicht in benutzerzugänglichen Referenzdocs anzeigen

Offene Fragen — vorgeschlagene Lösungen

#QuestionResolution
Q1Ist das Weglassen von color in #4821 absichtlich?Als absichtlich behandeln. Das Feld porten; NICHT in benutzerzugänglichen Docs erwähnen, außer als “available, internal”.
Q2Tolerantes DL7-Verhalten: dokumentieren oder hacken?Spiegeln. background: "true", maxTurns: "5", effort: "med" für die Parität akzeptieren, auch wenn undokumentiert. Tests hinzufügen.
Q3Warum unterscheidet sich die Isolation-Enum zwischen Agent-Schema und Background-Session-Schema?Die Abweichung im Code-Kommentar dokumentieren; “no isolation” = Feld weggelassen, kein Enum-Wert.
Q4Sollte --agents <json> (Plural, flagSettings) in v1 gemerged werden?Auf P4 verschieben. CLI-Oberfläche für Power-User; v1 liefert nur --agent <name> (Singular) aus, worum es in #4821 geht.
Q5Inner-tree- vs. Outer-tree-Vorrang für verschachtelte .qwen/agents/?Innermost-wins. Das versehentliche Outer-wins-Verhalten von Claude Code überschreiben. Test-Fixture in P1.
Q6tools- vs. disallowedTools-Vorrang: #4821 besagt “ignored if tools is set”; #4721 besagt “union with workflow floor”Registry sind dumme Daten. Der Parser bewahrt beide Felder unabhängig voneinander. Vorrangregeln leben am Dispatch-Site (Agent-Tool / Workflow). Löst den Widerspruch auf.
Q7Kanonische Form des Tool-Namens für den Workflow-disallowedTools-Floor — verifiziert gegen PR #4732 als ToolNames.SEND_MESSAGE, ToolNames.EXIT_PLAN_MODENicht die Sorge dieses PRs — liegt beim Workflow-PR. Nur in der Koordinationsmatrix dokumentieren.
Q8Beeinflusst die Close-Resolution von #2409 irgendetwas?Die Guidance “promote model + maxTurns to top-level” aus #2409 übernehmen. Bereits in diesen Plan eingearbeitet.
Q9Sollten extension-Level-Agenten in qwen-codes bestehendem SubagentLevel-Vorrang über builtin (aktuell) oder darunter bleiben (Claude Code hat kein Äquivalent)?extension > builtin beibehalten. Extensions sind vom Benutzer installiert; Built-ins sind Vendor-Default. Vom Benutzer installierte gewinnen.
Q10Sind die Issues #4821, #4721, #4732 für den von diesem Dokument vorgeschlagenen Vertrag vollständig spezifiziert?Einen Koordinations-Kommentar in #4821 posten, der auf dieses Dokument verlinkt, die Feld-für-Feld-Entscheidungen zusammenfasst und die Maintainer bittet, zu acken: (a) Schema-Parität mit den 16 Feldern von Claude Code 2.1.168, (b) D7 permissionMode/approvalMode-Bridge, (c) D2-Vorrangreihenfolge, (d) Registry-as-dumb-data-Lösung des tools/disallowedTools-Widerspruchs.

Koordinations-Action-Items

#ActionWhere
A1Feld-für-Feld-Zusammenfassung + 5 Entscheidungen in #4821 für Maintainer-Ack postenKommentar in #4821
A2Dieses Dokument in #4721 verlinken und auf die Phase-3-Matrix hinweisenKommentar in #4721
A3Sobald P1 dieses Ports gemerged ist, #4732 pingen, um zum umfangreicheren Resolver zu wechselnKommentar in PR #4732 (wenn bereit)
A4Strings-Grep für Schema-Drift-Erkennung gegen das nächste Claude Code Minor Release erneut ausführenfeature-reverse-Skill-Cronjob (bis dahin manuell)
Last updated on