From 98a8c57c8394af364a3a94e4345e7e28c537feff Mon Sep 17 00:00:00 2001 From: robert Date: Tue, 26 May 2026 09:49:40 -0300 Subject: [PATCH] fix: use platform llm grants for builder --- README.md | 6 +++--- agent.py | 13 ++++++++----- agent_builder/builder.py | 5 +++-- tests/test_builder_prompt.py | 7 +++++++ 4 files changed, 21 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 23d788e..cce41e3 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ Three platform-managed bits land on the inner skill via `RunContext`: | Field | Source | Required? | |---|---|---| | `ctx.workspace.bucket` | grant minted by the orchestrator | yes | -| `ctx.llm` | caller's LLM creds (Card declares `llm_provisioning=caller_provided`) | yes — the LLM bill goes to the caller | +| `ctx.llm` | platform-scoped LiteLLM grant (Card declares `llm_provisioning=platform`) | yes | | `ctx.cp_jwt` | caller's CP JWT (Card declares `wants_cp_jwt=True`) | yes — used by `cp_deploy_tarball` | The user opts into all three when they pick `agent-builder` from the @@ -55,8 +55,8 @@ marketplace — the dashboard already surfaces these on the agent card. ## Pricing -`$0.10 / call`, plus your LLM cost via your own provider. The deployed -agent is yours forever; subsequent invocations of YOUR agent run on +`$0.10 / call` during the current pricing model. The deployed +agent is yours forever; subsequent invocations of your agent run on their own pricing (whatever you set when generating it). ## How to call it diff --git a/agent.py b/agent.py index ad816ff..27408ac 100644 --- a/agent.py +++ b/agent.py @@ -10,8 +10,8 @@ This agent only works when invoked through the platform orchestrator, because it needs three things forwarded from the caller: 1. ``ctx.workspace.bucket`` — the user's MinIO bucket - 2. ``ctx.llm`` — caller's LLM creds (this is an expensive skill; - the user pays for inference directly via their own provider) + 2. ``ctx.llm`` — a platform-scoped LiteLLM grant token for the builder's + own DeepAgents loop 3. ``ctx.cp_jwt`` — the user's CP JWT, so cp_deploy_tarball can POST to /v1/agents/from-tarball as the user. """ @@ -53,15 +53,18 @@ class AgentBuilder(A2AAgent[BuilderConfig, NoAuth]): config_model = BuilderConfig auth_model = NoAuth tools_used = ("deepagents", "langgraph", "a2a-pack", "litellm", "microsandbox") - llm_provisioning = LLMProvisioning.CALLER_PROVIDED + llm_provisioning = LLMProvisioning.PLATFORM wants_cp_jwt = True workspace_access = WorkspaceAccess.dynamic( allowed_modes=(WorkspaceMode.READ_ONLY, WorkspaceMode.READ_WRITE_OVERLAY), ) pricing = Pricing( price_per_call_usd=0.10, - caller_pays_llm=True, - notes="LLM cost passes through; deployed agent is yours forever.", + caller_pays_llm=False, + notes=( + "Uses a platform-scoped LiteLLM grant for the builder's own " + "reasoning loop; deployed agent is yours forever." + ), ) @skill( diff --git a/agent_builder/builder.py b/agent_builder/builder.py index 6f2dc18..260f617 100644 --- a/agent_builder/builder.py +++ b/agent_builder/builder.py @@ -27,8 +27,9 @@ class BuilderContext: project_prefix: str | None = None workspace: Any | None = None grant_token: str | None = None - # Optional caller-provided LLM creds (when the outer platform Card - # declares llm_provisioning=caller_provided). Falls back to settings. + # LLM creds forwarded by the orchestrator according to this agent's Card. + # The builder itself uses platform grants; generated agents usually use + # caller-provided creds. Falls back to settings for local development. llm_base_url: str | None = None llm_api_key: str | None = None llm_model: str | None = None diff --git a/tests/test_builder_prompt.py b/tests/test_builder_prompt.py index 34ba36d..7a1d5f0 100644 --- a/tests/test_builder_prompt.py +++ b/tests/test_builder_prompt.py @@ -24,6 +24,13 @@ class BuilderPromptTests(unittest.TestCase): ) self.assertIn("stream=True", agent_source) + def test_outer_builder_uses_platform_llm_grants(self) -> None: + agent_source = Path(__file__).resolve().parents[1].joinpath("agent.py").read_text() + + self.assertIn("llm_provisioning = LLMProvisioning.PLATFORM", agent_source) + self.assertIn("caller_pays_llm=False", agent_source) + self.assertIn("platform-scoped LiteLLM grant", agent_source) + def test_builder_defaults_raise_timeout_budget(self) -> None: config_source = Path(__file__).resolve().parents[1].joinpath( "agent_builder/config.py"