From 2f753e123f1b41122bf5e17d2151b5ecd6fd45f3 Mon Sep 17 00:00:00 2001 From: "fangyaozheng@bytedance.com" Date: Wed, 24 Jun 2026 10:33:58 +0800 Subject: [PATCH 1/3] feat(harness): apply agent description at harness agent init Adds a `description` field to HarnessConfig (defaulting to the VeADK default description), maps it to the DESCRIPTION env var, and passes it to the Agent constructed at harness startup. This lets a deployed harness carry a configured agent description (e.g. for A2A discovery) instead of always using the default. Co-Authored-By: Claude Opus 4.8 --- veadk/cloud/harness_app/agent.py | 1 + veadk/cloud/harness_app/types.py | 3 ++- veadk/cloud/harness_app/utils.py | 2 ++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/veadk/cloud/harness_app/agent.py b/veadk/cloud/harness_app/agent.py index 7d4ed9f7..669976de 100644 --- a/veadk/cloud/harness_app/agent.py +++ b/veadk/cloud/harness_app/agent.py @@ -22,6 +22,7 @@ Environment variables: MODEL_NAME Reasoning model name. Default: VeADK default model. SYSTEM_PROMPT Agent instruction. Default: VeADK default instruction. + DESCRIPTION Agent description (e.g. for A2A discovery). Default: VeADK default description. TOOLS Comma-separated built-in tool names, e.g. "web_search,link_reader". SKILLS Comma-separated skill names, e.g. "data-visualization-cloud,...". RUNTIME Agent runtime backend: "adk" (default) or "codex". diff --git a/veadk/cloud/harness_app/types.py b/veadk/cloud/harness_app/types.py index a310aec5..dd36cec0 100644 --- a/veadk/cloud/harness_app/types.py +++ b/veadk/cloud/harness_app/types.py @@ -30,7 +30,7 @@ from pydantic import BaseModel, Field from veadk.consts import DEFAULT_MODEL_AGENT_NAME -from veadk.prompts.agent_default_prompt import DEFAULT_INSTRUCTION +from veadk.prompts.agent_default_prompt import DEFAULT_DESCRIPTION, DEFAULT_INSTRUCTION class HarnessOverrides(BaseModel): @@ -82,6 +82,7 @@ class HarnessConfig(HarnessOverrides): app_name: str = Field(default="harness_app", alias="name") system_prompt: str = Field(default=DEFAULT_INSTRUCTION) + description: str = Field(default=DEFAULT_DESCRIPTION) knowledgebase_type: str = Field(default="") longterm_memory_type: str = Field(default="") shortterm_memory_type: str = Field(default="local") diff --git a/veadk/cloud/harness_app/utils.py b/veadk/cloud/harness_app/utils.py index e4674e30..e9f401e1 100644 --- a/veadk/cloud/harness_app/utils.py +++ b/veadk/cloud/harness_app/utils.py @@ -111,6 +111,7 @@ def _load_builtin_tool(name: str) -> Any: "tools": "TOOLS", "skills": "SKILLS", "system_prompt": "SYSTEM_PROMPT", + "description": "DESCRIPTION", "runtime": "RUNTIME", "structured_tool_calls": "STRUCTURED_TOOL_CALLS", "include_tools_every_turn": "INCLUDE_TOOLS_EVERY_TURN", @@ -318,6 +319,7 @@ def _assemble_agent(config: HarnessConfig) -> tuple[Agent, ShortTermMemory]: name="harness_agent", model_name=config.model_name, instruction=config.system_prompt, + description=config.description, tools=tools, runtime=config.runtime, enable_responses=config.structured_tool_calls, From 745f490db198e6ec22c2f1df8838c635e7adf9da Mon Sep 17 00:00:00 2001 From: "fangyaozheng@bytedance.com" Date: Wed, 24 Jun 2026 10:46:32 +0800 Subject: [PATCH 2/3] test(harness): include `description` in HarnessConfig field contract The creation-time field set in test_adds_creation_time_fields must list the new `description` field added to HarnessConfig. Co-Authored-By: Claude Opus 4.8 --- tests/cloud/test_harness_app_contract.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/cloud/test_harness_app_contract.py b/tests/cloud/test_harness_app_contract.py index 6719c5cd..e722b5da 100644 --- a/tests/cloud/test_harness_app_contract.py +++ b/tests/cloud/test_harness_app_contract.py @@ -91,6 +91,7 @@ def test_extends_overrides(self): def test_adds_creation_time_fields(self): assert set(_fields(HarnessConfig)) == set(_fields(HarnessOverrides)) | { "app_name", + "description", "knowledgebase_type", "longterm_memory_type", "shortterm_memory_type", From c7de7a9dec0ef17ca93b0ecd9f05d8ce1b3259e9 Mon Sep 17 00:00:00 2001 From: "fangyaozheng@bytedance.com" Date: Wed, 24 Jun 2026 10:57:35 +0800 Subject: [PATCH 3/3] feat(harness): name the agent after the harness instead of a constant The harness agent was always named `harness_agent`, so every deployed harness produced an identically-named A2A agent card. Derive the agent name from the harness name (HARNESS_NAME / app_name) via `agent_name_from_harness`, normalizing it to a valid ADK identifier (ADK requires a Python identifier and forbids "user"; harness names also allow "-" and may start with a digit, e.g. "oauth-test" -> "oauth_test"). Co-Authored-By: Claude Opus 4.8 --- tests/cloud/test_harness_app_contract.py | 29 +++++++++++++++++++++++- veadk/cloud/harness_app/utils.py | 22 +++++++++++++++++- 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/tests/cloud/test_harness_app_contract.py b/tests/cloud/test_harness_app_contract.py index e722b5da..7d70109f 100644 --- a/tests/cloud/test_harness_app_contract.py +++ b/tests/cloud/test_harness_app_contract.py @@ -34,7 +34,11 @@ RunAgentRequest, ) from veadk.cloud.harness_app.env_mapping import to_runtime_env -from veadk.cloud.harness_app.utils import config_from_env, split_csv +from veadk.cloud.harness_app.utils import ( + agent_name_from_harness, + config_from_env, + split_csv, +) from veadk.consts import DEFAULT_MODEL_AGENT_NAME from veadk.prompts.agent_default_prompt import DEFAULT_INSTRUCTION @@ -233,3 +237,26 @@ def test_empty_string_is_empty_list(self): def test_drops_blank_segments(self): assert split_csv("a,, ,b") == ["a", "b"] + + +class TestAgentNameFromHarness: + """The agent name (and thus the A2A card name) is derived from the harness + name, normalized to a valid ADK identifier.""" + + def test_identifier_passes_through(self): + assert agent_name_from_harness("harness_app") == "harness_app" + + def test_hyphens_become_underscores(self): + assert agent_name_from_harness("oauth-test") == "oauth_test" + + def test_leading_digit_is_prefixed(self): + assert agent_name_from_harness("2048-bot") == "_2048_bot" + + def test_reserved_user_is_escaped(self): + assert agent_name_from_harness("user") == "user_" + + def test_result_is_always_a_valid_identifier(self): + for raw in ["oauth-test", "2048-bot", "user", "a.b c", ""]: + name = agent_name_from_harness(raw) + assert name.isidentifier(), raw + assert name != "user" diff --git a/veadk/cloud/harness_app/utils.py b/veadk/cloud/harness_app/utils.py index e9f401e1..7fb42e5e 100644 --- a/veadk/cloud/harness_app/utils.py +++ b/veadk/cloud/harness_app/utils.py @@ -27,6 +27,7 @@ import io import os +import re import shutil import tempfile import zipfile @@ -66,6 +67,7 @@ "HarnessConfig", "HarnessOverrides", "split_csv", + "agent_name_from_harness", "build_skill_toolset", "SkillLoadError", "ToolLoadError", @@ -140,6 +142,24 @@ def split_csv(value: str) -> list[str]: return [item.strip() for item in value.split(",") if item.strip()] +def agent_name_from_harness(harness_name: str) -> str: + """Derive a valid ADK agent name from the harness name. + + The agent name becomes the A2A agent card's ``name``, so it should reflect + the harness rather than a shared constant. ADK requires the agent ``name`` + to be a Python identifier (letters, digits, underscores; not starting with a + digit) and forbids ``"user"``, while harness names also allow ``-`` and may + start with a digit. Normalize: map every non-identifier char to ``_`` and + prefix a digit-leading or empty name with ``_``. + + ``"oauth-test"`` -> ``"oauth_test"``; ``"2048-bot"`` -> ``"_2048_bot"``. + """ + name = re.sub(r"[^0-9A-Za-z_]", "_", harness_name or "") + if not name or name[0].isdigit(): + name = f"_{name}" + return f"{name}_" if name == "user" else name + + def _download_and_extract_skill(skill: str, dest_dir: Path) -> Path: """Download a skill zip from the skill hub and extract it. @@ -316,7 +336,7 @@ def _assemble_agent(config: HarnessConfig) -> tuple[Agent, ShortTermMemory]: ) agent = Agent( - name="harness_agent", + name=agent_name_from_harness(config.app_name), model_name=config.model_name, instruction=config.system_prompt, description=config.description,