From 4cef2ef76fffc717cd817dbbe193df15ced046da Mon Sep 17 00:00:00 2001 From: robert Date: Mon, 8 Jun 2026 10:39:50 -0300 Subject: [PATCH] Handle deterministic agents without LLM creds --- agent_builder/builder.py | 32 ++++++++++++------- .../skills/a2apack-agent-authoring/SKILL.md | 6 ++++ .../skills/agent-quality-review/SKILL.md | 9 ++++-- tests/test_builder_prompt.py | 11 +++++-- 4 files changed, 42 insertions(+), 16 deletions(-) diff --git a/agent_builder/builder.py b/agent_builder/builder.py index a8bbab7..35045e3 100644 --- a/agent_builder/builder.py +++ b/agent_builder/builder.py @@ -49,20 +49,28 @@ Given a user description, you write a complete agent project under the user's workspace at ``agents//`` and then deploy it through the control plane. -The default starter is the current a2a-pack DeepAgents scaffold. It declares -``LLMProvisioning.PLATFORM``, reads the caller's saved LLM credential from - ``ctx.llm``, resolves the model through ``create_a2a_deep_agent``, and wires a small - tool-calling DeepAgent with the A2A DeepAgents model middleware -middleware. Do not recreate this from memory: call ``init_agent_template`` first +The default starter is the current a2a-pack DeepAgents scaffold. For agents +that call an LLM, it declares ``LLMProvisioning.PLATFORM``, reads the caller's +saved LLM credential from ``ctx.llm``, resolves the model through +``create_a2a_deep_agent``, and wires a small tool-calling DeepAgent with the +A2A DeepAgents model middleware. Do not recreate this from memory: call +``init_agent_template`` first and modify the generated files. -Default hosted generated/user agents to ``LLMProvisioning.PLATFORM`` and -``caller_pays_llm=True`` so main-agent handoffs forward the caller's saved LLM -credential for the callee. The generated agent must still read ``ctx.llm`` and must never -read ``A2A_LITELLM_KEY``, ``OPENAI_API_KEY``, LiteLLM master keys, or provider -keys directly. ``LLMProvisioning.CALLER_PROVIDED`` is still acceptable for - explicit BYOK wording. Always make missing ``ctx.llm.api_key`` a clear setup/config result before constructing - a DeepAgents model. +For hosted generated/user agents that call an LLM, default to +``LLMProvisioning.PLATFORM`` and ``caller_pays_llm=True`` so main-agent +handoffs forward the caller's saved LLM credential for the callee. The +generated agent must still read ``ctx.llm`` and must never read +``A2A_LITELLM_KEY``, ``OPENAI_API_KEY``, LiteLLM master keys, or provider keys +directly. ``LLMProvisioning.CALLER_PROVIDED`` is still acceptable for explicit +BYOK wording. Always make missing ``ctx.llm.api_key`` a clear setup/config +result before constructing a DeepAgents model. + +For explicitly deterministic/local-logic agents that do not call an LLM, remove +the starter's ``LLMProvisioning`` import and class ``llm_provisioning`` +declaration, do not read ``ctx.llm``, and set +``Pricing(..., caller_pays_llm=False, ...)``. A no-LLM agent must be callable +without requiring the user's LLM credential. ``Pricing`` accepts only ``price_per_call_usd``, ``caller_pays_llm``, and ``notes``. Never set ``runtime.pricing.compute`` or ``runtime.pricing.total_usd`` in generated source; those are derived later by diff --git a/agent_builder/skills/a2apack-agent-authoring/SKILL.md b/agent_builder/skills/a2apack-agent-authoring/SKILL.md index 2bebfa5..85a5988 100644 --- a/agent_builder/skills/a2apack-agent-authoring/SKILL.md +++ b/agent_builder/skills/a2apack-agent-authoring/SKILL.md @@ -31,6 +31,12 @@ never read `A2A_LITELLM_KEY`, `OPENAI_API_KEY`, provider keys, or platform secrets directly. If `ctx.llm.api_key` is empty, return a clear setup/config result before constructing a model or calling `create_a2a_deep_agent`. +For explicitly deterministic/local-logic agents that do not call an LLM, omit +the concrete class `llm_provisioning` declaration, remove unused +`LLMProvisioning` imports, do not read `ctx.llm`, and set +`Pricing(..., caller_pays_llm=False, ...)`. A no-LLM agent must be callable +without requiring the user's LLM credential. + `Pricing` only accepts `price_per_call_usd`, `caller_pays_llm`, and `notes`. Do not put `runtime.pricing.compute`, `runtime.pricing.total_usd`, `compute=`, or `total_usd=` in generated `agent.py`; the platform derives those values from diff --git a/agent_builder/skills/agent-quality-review/SKILL.md b/agent_builder/skills/agent-quality-review/SKILL.md index bbe0d48..06f273f 100644 --- a/agent_builder/skills/agent-quality-review/SKILL.md +++ b/agent_builder/skills/agent-quality-review/SKILL.md @@ -30,14 +30,19 @@ Check these items: arguments. - The public schema is small and stable. No unbounded command strings, hidden prompt fragments, or provider credentials as public inputs. -- The implementation reads `ctx.llm` for both `CALLER_PROVIDED` and +- LLM-backed implementations read `ctx.llm` for both `CALLER_PROVIDED` and `PLATFORM` LLM provisioning. `PLATFORM_OR_CALLER_PROVIDED` is acceptable only for trusted platform/meta agents and must also read `ctx.llm`. -- Hosted generated/user agents should default to +- Hosted generated/user agents that call an LLM should default to `llm_provisioning = LLMProvisioning.PLATFORM` and `Pricing(..., caller_pays_llm=True, ...)`. `LLMProvisioning.CALLER_PROVIDED` is acceptable for explicit BYOK wording. In both modes, the code must use `ctx.llm` and never read LiteLLM/provider keys directly. +- Deterministic/local-logic agents that do not call an LLM omit the concrete + `llm_provisioning` declaration, remove unused `LLMProvisioning` imports, do + not read `ctx.llm`, and use `Pricing(..., caller_pays_llm=False, ...)`. + Reject no-LLM agents that would require a user's LLM credential before + invoking deterministic skills. - `Pricing` contains only `price_per_call_usd`, `caller_pays_llm`, and `notes`. Reject `runtime.pricing.compute`, `runtime.pricing.total_usd`, `compute=`, or `total_usd=` in `agent.py`; those are derived platform fields. diff --git a/tests/test_builder_prompt.py b/tests/test_builder_prompt.py index 64279b6..fa18188 100644 --- a/tests/test_builder_prompt.py +++ b/tests/test_builder_prompt.py @@ -185,16 +185,23 @@ class BuilderPromptTests(unittest.TestCase): self.assertIn(f"a2a-pack>={A2A_PACK_MIN_VERSION}", requirements) self.assertIn("a2a-pack>={A2A_PACK_MIN_VERSION}", tools_source) - def test_prompt_defaults_generated_agents_to_user_llm_credentials(self) -> None: + def test_prompt_routes_llm_and_no_llm_agents_differently(self) -> None: self.assertIn("LLMProvisioning.PLATFORM", SYSTEM_PROMPT) self.assertIn("caller_pays_llm=True", SYSTEM_PROMPT) - self.assertIn("main-agent handoffs forward the caller's saved LLM", SYSTEM_PROMPT) + self.assertIn("handoffs forward the caller's saved LLM credential", SYSTEM_PROMPT) self.assertIn("ctx.llm.api_key", SYSTEM_PROMPT) + self.assertIn("deterministic/local-logic agents", SYSTEM_PROMPT) + self.assertIn("caller_pays_llm=False", SYSTEM_PROMPT) + self.assertIn("A no-LLM agent must be callable", SYSTEM_PROMPT) + self.assertIn("without requiring the user's LLM credential", SYSTEM_PROMPT) files = _builder_skill_files() self.assertIn("LLMProvisioning.PLATFORM", files["a2apack-agent-authoring/SKILL.md"]) self.assertIn("caller_pays_llm=True", files["a2apack-agent-authoring/SKILL.md"]) + self.assertIn("caller_pays_llm=False", files["a2apack-agent-authoring/SKILL.md"]) self.assertIn("ctx.llm.api_key", files["agent-quality-review/SKILL.md"]) + self.assertIn("Deterministic/local-logic agents", files["agent-quality-review/SKILL.md"]) + self.assertIn("require a user's LLM credential", files["agent-quality-review/SKILL.md"]) def test_prompt_rejects_platform_derived_pricing_fields(self) -> None: self.assertIn("runtime.pricing.compute", SYSTEM_PROMPT)