DO NOT MERGE: thread per-agent llm_metadata to AgentConfig::with_metadata#328
Draft
brentrager wants to merge 2 commits into
Draft
DO NOT MERGE: thread per-agent llm_metadata to AgentConfig::with_metadata#328brentrager wants to merge 2 commits into
brentrager wants to merge 2 commits into
Conversation
Wire the operator to the new engine metadata seam so a host resolver can tag
each agent's LLM spend. A per-agent `AgentBehaviorConfig` gains
`llm_metadata: Option<serde_json::Map<String, Value>>` (serde default, so
absent ⇒ None, byte-identical to before); the reference handler digests it
into `TurnRequest.request_metadata`, and the runner forwards it to the
engine via `AgentConfig::with_metadata(...)` alongside the existing
`with_model_ceiling`. `None`/empty ⇒ no `metadata` on the wire.
Nothing in this repo populates `llm_metadata` yet — the analyst-agent
resolver that sets `{ smooai_agent_slug: ... }` lives in the monorepo
(copilot-ws). This is the dormant seam it plugs into. DB-row-resolved agents
(`from_row_values`) carry no metadata; it is set programmatically only.
DO NOT MERGE until smooth-operator-core with `AgentConfig::with_metadata`
publishes. A temporary `[patch.crates-io]` points the core dep at that PR's
branch so the workspace + smoke build compile against the real new API;
before merge, bump the core pin and delete the patch. No changeset included —
the release is held.
Tests: `AgentBehaviorConfig` deserializes `llm_metadata`, defaults to None,
and reflects it in `is_empty()`.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018tRq2Rge3gGHCjJdNx6xDD
|
…completions body Drives the REAL LlmClient (built by the engine from AgentConfig) against a local mock that captures the outbound body: analyst-shaped request_metadata arrives as top-level `metadata.smooai_agent_slug`; None sends no `metadata` key. Covers the one link the unit tests bypass — the run loop's `AgentConfig::with_metadata` → built-client path (mock-provider tests skip it). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tRq2Rge3gGHCjJdNx6xDD
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Wires this operator to the new engine metadata seam so a host resolver can tag each agent's LLM spend:
AgentBehaviorConfiggainsllm_metadata: Option<serde_json::Map<String, Value>>(#[serde(default)]— absent ⇒None, byte-identical to before; included inis_empty()).TurnRequest.request_metadatafield.AgentConfig::with_metadata(...), right beside the existingwith_model_ceiling— so it rides both the streaming and non-streaming request bodies core builds.None/empty ⇒ nometadataon the wire.Why
Per-agent LLM spend attribution. The LiteLLM gateway records a top-level
metadataobject on its spend logs; a monorepo billing cron partitions bymetadata.smooai_agent_slug. The core PR added the passthrough; this PR lets a resolved per-agent config carry the tag through to it. Tracking: pearlth-b8ea4c(epic SMOODEV-2708).Dormant in this repo: nothing here populates
llm_metadata— the analyst-agent resolver that sets{ smooai_agent_slug: "observability-analyst" }lives in the monorepo (copilot-ws, not touched here). DB-row-resolved agents (from_row_values) carry no metadata; it is set programmatically only. This is the seam that resolver plugs into.Mirrors the
with_model_ceilingpattern (no trait widening)The
Arc<dyn LlmProvider>the runner holds is only used for the injected/mock path. For a live turn the engine builds the concreteLlmClientitself, fromAgentConfig— that's howwith_model_ceilingalready reaches the wire (AgentConfig::with_model_ceiling→ engine buildsLlmClient::new(...).with_model_ceiling(...)in both run loops). Metadata mirrors it exactly:AgentConfig::with_metadata(in the core PR) + a runner call here. No new trait method needed.[patch.crates-io]— the DO NOT MERGE bitThe core methods (
AgentConfig::with_metadata/LlmClient::with_metadata) are unpublished.rust/Cargo.tomlhas a temporary[patch.crates-io]pointingsmooai-smooth-operator-coreat the core PR's branch so the workspace + kind smoke build compile against the real new API. It cannot be removed without breaking the compile, so it stays and is marked loudly inline. Before merge: bump thesmooai-smooth-operator-corepin to the version that ships those methods, then delete the patch section.Verification
cargo test -p smooai-smooth-operator -p smooai-smooth-operator-server -p smooai-smooth-operator-lambda— 470 passed, 1 ignored (against the core branch via the patch).cargo fmt --all --checkclean;cargo clippyclean on touched files. Two test altitudes:AgentBehaviorConfigdeserializesllm_metadata, defaults toNone, reflects it inis_empty().tests/llm_metadata_wire.rs) — drives the REALLlmClient(built by the engine fromAgentConfig) against a capturing mock: analyst-shapedrequest_metadataarrives as top-levelmetadata.smooai_agent_slug;Nonesends nometadatakey. Covers the one link the unit tests bypass — the run loop'sAgentConfig::with_metadata→ built-client path.Release / changeset
No changeset included — the release is held for the release owner (same as the core PR). Whoever finalizes adds the
@smooai/smooth-operatorchangeset when flipping the pin. Note the repo'ssync-versions.mjsstep-3 skip convention for operator-core releases applies at that time.🤖 Generated with Claude Code
https://claude.ai/code/session_018tRq2Rge3gGHCjJdNx6xDD