Topic 1 (HITL): native HITL is GA (ctx.request_info/@response_handler/
run(responses=); GroupChatBuilder.with_request_info → AgentApprovalExecutor),
but durable checkpoint-resume is fragile (open #5818/#6127/#6372 into 1.9.0)
→ capture verdict out-of-band in VerdictStore, defer checkpointing off MVP path.
Topic 2 (MCP citation): REVERSES brief lean — official server-filesystem cannot
cite (raw text + bare paths) → build thin custom local-folder MCP server
returning {file,locator,snippet,score} over a framework-agnostic in-process
retriever (D7 seam). Corrected docs error: ContextProvider(source_id) +
before_run/after_run + extend_instructions(source_id,...) DO exist in 1.9.0.
Topic 3 (local chat client): use OpenAIChatCompletionClient(base_url) NON-STREAMING
(not OpenAIChatClient/Responses) — installed, 0 new deps, UsageDetails None-safe
and populated non-streaming. Native OllamaChatClient is --pre fallback (spike-gated).
validator-as-retry mitigates weak small-model tool-calling; Intel-CPU = plumbing only.
All grounded in installed 1.9.0 source (source wins over Learn docs). Gemini
bridge unavailable (MCP SDK predates Google May-2026 API change).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fif1r1En5W542HbZV88yMH
237 lines
21 KiB
Markdown
237 lines
21 KiB
Markdown
---
|
||
type: trekresearch-brief
|
||
created: 2026-06-24
|
||
question: "How does agent-framework-core 1.9.0 run agents against a local model on the free/local profile (OpenAI-compatible endpoint, Ollama, or other local chat client), and does that path populate UsageDetails token counts or return None?"
|
||
confidence: 0.80
|
||
dimensions: 6
|
||
mcp_servers_used: [microsoft-learn, tavily]
|
||
local_agents_used: [installed-source-introspection (orchestrator, main context)]
|
||
external_agents_used: [docs-researcher, community-researcher, security-researcher, contrarian-researcher, gemini-bridge (unavailable)]
|
||
topic: 3
|
||
brief: .claude/projects/2026-06-24-fase2-mvp-vertical-slice/brief.md
|
||
---
|
||
|
||
# Real local-profile chat client for agent-framework 1.9.0 + UsageDetails
|
||
|
||
> Generated by trekresearch (Voyage 5.6.0) on 2026-06-24. Topic 3 of 3 for the
|
||
> Fase 2 MVP-vertical-slice brief. Scope: external swarm + installed-source
|
||
> introspection (installed API truth wins). Gemini unavailable (MCP SDK broke on
|
||
> Google's May-2026 API change).
|
||
|
||
## Research Question
|
||
|
||
How does `agent-framework-core` 1.9.0 (+ `agent-framework-openai`) run agents
|
||
against a LOCAL model on the free/local profile, and does that path populate
|
||
`UsageDetails` token counts or return `None`? Decision it feeds: the backend-profile
|
||
abstraction (local path), the budget middleware's `None`-handling, the end-to-end
|
||
run, and cost-discipline verification.
|
||
|
||
## Executive Summary
|
||
|
||
**Two local paths exist, and the brief's framing needs two corrections.** Path A —
|
||
`OpenAIChatCompletionClient(base_url=<local /v1>)` — is **installed (1.8.2), needs
|
||
zero new dependencies, and populates `UsageDetails` None-safely from the local
|
||
server's `usage` object**. Path B — the native `OllamaChatClient` — is a **`--pre`
|
||
beta package (`agent-framework-ollama 1.0.0b260521`) that is NOT installed** and
|
||
adds the `ollama` client dependency. **Correction 1 (client):** use
|
||
`OpenAIChatCompletionClient` (Chat Completions API — the surface Ollama/LM Studio
|
||
`/v1` exposes), NOT `OpenAIChatClient` (Responses API). **Correction 2 (streaming):**
|
||
run the debate/validator path **NON-STREAMING** — that simultaneously dodges the
|
||
well-documented `/v1` streaming+tool-calling drop bugs AND the streaming
|
||
`usage=None` problem (`UsageDetails` is reliably populated only non-streaming).
|
||
**Token accounting works** (confirmed in installed source), so the "real token
|
||
accounting" success criterion is satisfiable on the local profile. The real limits
|
||
are model capability and Intel-Mac CPU speed: small CPU-runnable models are weak at
|
||
tool-calling, and an Intel Mac (no Metal/MLX, ~4–6 tok/s on 7B) makes a multi-round
|
||
debate take minutes — so local is for plumbing/smoke verification, with
|
||
representative-quality runs pushed to Foundry. Confidence 0.80.
|
||
|
||
## Dimensions
|
||
|
||
### 1. Local chat-client paths — Confidence: high
|
||
|
||
**Installed-source findings (ground truth):**
|
||
- `agent-framework-openai` 1.8.2 exports **both** `OpenAIChatClient` (Responses API) and `OpenAIChatCompletionClient` (Chat Completions API) — both import OK in the venv. `RawOpenAIChatClient`/`RawOpenAIChatCompletionClient` are the lower-level variants.
|
||
- Both accept `base_url` (→ env `OPENAI_BASE_URL`) and `api_key` (required even for local — pass a dummy like `"ollama"`/`"not-needed"`). `model_id` selects the model.
|
||
- The native Ollama path: `agent_framework/ollama/__init__.pyi` re-exports `OllamaChatClient`, `OllamaChatOptions`, `OllamaEmbeddingClient`, … from `agent_framework_ollama` — but **`agent_framework_ollama` is NOT installed** (`import agent_framework_ollama` → ModuleNotFoundError; only the lazy re-export stub exists). Installing it (`uv add agent-framework-ollama --prerelease=allow`) pulls `ollama >=0.5.3,<0.5.4`.
|
||
|
||
**External findings:**
|
||
- **Correct client for local `/v1`:** Ollama/LM Studio/vLLM expose a Chat Completions surface (`/v1/chat/completions`), so `OpenAIChatCompletionClient` is the technically correct client; `OpenAIChatClient` targets the Responses API which local servers don't expose. Two official MS docs disagree on which to use for Ollama — **flagged conflict; the Chat-Completions client is correct for `/v1`.** <https://learn.microsoft.com/agent-framework/integrations/openai-endpoints>
|
||
- Official local-endpoint table: Ollama `http://localhost:11434/v1/`, LM Studio `http://localhost:1234/v1/`, vLLM `http://localhost:8000/v1/`. The `/v1/` suffix matters; the native `OllamaChatClient` uses `:11434` WITHOUT `/v1`.
|
||
- Native `OllamaChatClient` is **preview/beta** (`1.0.0b260521`, "4-Beta", `--pre`), wraps `ollama.AsyncClient` (native `/api/chat`), `OTEL_PROVIDER_NAME='ollama'`. MS docs describe it as having "full support for function tools and streaming." <https://learn.microsoft.com/agent-framework/agents/providers/ollama>
|
||
|
||
### 2. UsageDetails population — Confidence: high
|
||
|
||
**Installed-source findings (the load-bearing answer):**
|
||
- `OpenAIChatCompletionClient`: `_chat_completion_client.py:705` — `usage_details=self._parse_usage_from_openai(response.usage) if response.usage else None`. **None-safe.** `_parse_usage_from_openai` (`:757-776`) maps `usage.prompt_tokens`→`input_token_count`, `usage.completion_tokens`→`output_token_count`, `usage.total_tokens`→`total_token_count` (+ reasoning/cached/audio details).
|
||
- `UsageDetails` is the TypedDict in `agent_framework/_types.py` (`input/output/total_token_count: int | None`); `add_usage_details(u1, u2)` helper exists (`_types.py:417`) — use it to accumulate across calls in the shared budget meter. Lands on `response.usage_details` (`_types.py:547`); read `response.usage_details["total_token_count"]` (None-safe).
|
||
- Streaming: `_chat_completion_client.py:724-726` — `if chunk.usage: Content.from_usage(...)` — usage only arrives if the provider emits a usage chunk.
|
||
|
||
**External findings:**
|
||
- **Non-streaming:** Ollama/LM Studio/llama.cpp `/v1` all return a populated `usage` object → `UsageDetails` populated. Confirmed multi-source. <https://docs.ollama.com/api/openai-compatibility>
|
||
- **Streaming:** OpenAI semantics require `stream_options={"include_usage": true}`; whether MAF sets it automatically is **unverified** — so streamed `UsageDetails` may be `None`. → run non-streaming for the accounting path.
|
||
- **Native `OllamaChatClient`:** maps Ollama's `prompt_eval_count`→input, `eval_count`→output; **`total_token_count` is NOT set (derive it)**. Streaming updates carry no usage. <https://docs.ollama.com/api/usage>
|
||
- MAF usage plumbing is young: "Fix OTel usage detail attributes" landed in python-1.8.0 (already in our 1.9.0).
|
||
|
||
### 3. Tool-calling / structured output on local models — Confidence: high (load-bearing risk)
|
||
|
||
**External findings (strong multi-source pattern):**
|
||
- Small CPU-runnable models are markedly weak at tool-calling: 7–8B tool-selection F1 ≈ 0.48–0.57; failure modes = never calling the tool (answers from memory), emitting the tool call as **plain text/JSON** instead of a structured `tool_calls` object, and argument type drift (`"5"` vs `5`). <https://www.docker.com/blog/local-llm-tool-calling-a-practical-evaluation/>
|
||
- Qwen3-class is the most reliable small family (F1 ≈ 0.93 in the same eval); MAF docs explicitly bless `qwen3:4b` / `llama3.2` for tools and warn "not all models support function calling." `num_ctx ≥ 32k` improves reliability.
|
||
- Streaming + tools on `/v1` historically silently dropped/leaked tool calls (5+ repos; fixed 2025-05-28 but a 0.12.3 regression remains) — **another reason to run non-streaming.** <https://ollama.com/blog/streaming-tool>
|
||
- Structured output: `response_format: type[BaseModel]` is available on both local-path clients, but reliability is model-dependent.
|
||
- MAF bug #1772: `OpenAIChatClient` + Ollama + ChatContext middleware inserts the system prompt twice — verify against 1.9.0; the native client may avoid it. <https://github.com/microsoft/agent-framework/issues/1772>
|
||
|
||
**Design fit:** our **blocking Pydantic validator becomes the mitigation** — it must retry/repair on parse failure, turning model unreliability into a bounded retry loop rather than silent corruption.
|
||
|
||
### 4. Intel-Mac CPU performance reality — Confidence: medium-high
|
||
|
||
**External findings:**
|
||
- Intel Macs run Ollama **CPU-only** (no Metal/MLX — Apple-Silicon-only). Reported ~4–6 tok/s on a 7B model (i9 2019); 3–8 tok/s CPU-only generally — "usable for testing, painful for actual work." <https://localaimaster.com/blog/ollama-system-requirements>
|
||
- A maker-checker debate (N agents × M rounds × tool round-trips × validator retries) at single-digit tok/s runs into minutes per candidate.
|
||
|
||
**Conclusion:** Intel-Mac local is a **plumbing-verification + smoke-test** environment (does the slice wire up, do tool calls fire, does the validator parse, does usage come back?), not an iterative/batch run environment — exactly matching D6 ("develop on local, verify minimally on Foundry"). Keep local runs tiny: 1 maker + 1 checker, ≤3 rounds, tiny synthetic data, hard token caps, a 3–4B model.
|
||
|
||
### 5. Determinism boundary — Confidence: high
|
||
|
||
- Local LLM output is non-deterministic even at temperature 0 / fixed seed (documented edge cases). This does **not** conflict with the success criterion **iff** the determinism criterion is scoped to the **validator + Monte-Carlo** (fixed seed + fixed candidate input → identical accept/reject + identical P10/P50/P90), NOT to the LLM that produces the candidate.
|
||
- **Trap to avoid:** never assert end-to-end determinism *through* the LLM. Golden tests pin the validator's *inputs*, not the LLM's *outputs*. Our architecture already separates "agents propose (non-deterministic) → validator decides (deterministic)", so the boundary is sound — make it explicit in the test design.
|
||
|
||
### 6. Security / no-egress posture — Confidence: high
|
||
|
||
**External findings:**
|
||
- `OpenAIChatCompletionClient(base_url=localhost)` keeps inference on-box; the OpenAI SDK sends only to `base_url` and never contacts `api.openai.com`; a dummy `api_key` is safe. **No new dependency** (uses already-installed `agent-framework-openai`). This is the lower-supply-chain-cost path.
|
||
- Native `agent-framework-ollama` is official MS but **beta** and adds the `ollama` client dep (pinned `<0.5.4`) — a supply-chain + GA-discipline cost.
|
||
- Ollama daemon: bind **`127.0.0.1`** (never `0.0.0.0` — it has NO auth by design, CVE-2025-63389); pin Ollama **≥ 0.17.1** (clears CVE-2024-37032 "Probllama" RCE, the 39719-22 set, and CVE-2026-7482 "Bleeding Llama" GGUF heap-read); keep `OLLAMA_DEBUG` unset (else prompts are written to `server.log`).
|
||
- **Model-pull is explicit egress** (downloads from a registry) — document it in provenance; not "silent" but real. Air-gap option: pre-stage model blobs offline. LM Studio persists chats/RAG locally by default (treat as data-at-rest).
|
||
- No native telemetry/phone-home from MAF client (instrumentation off by default), Ollama (no inference telemetry), or LM Studio (off by default).
|
||
|
||
## External Knowledge
|
||
|
||
### Best Practice
|
||
- Local wiring: `OpenAIChatCompletionClient(base_url="http://127.0.0.1:11434/v1/", api_key="ollama", model_id="qwen3:4b")` (non-streaming); or native `OllamaChatClient(host=..., model_id=...)` after `--pre` install. Cost: no native cost metric — derive `cost = tokens × per-model price` (local = 0); token usage on OTel `gen_ai.client.token.usage`.
|
||
|
||
### Known Issues
|
||
- Streaming+tools `/v1` drop/leak (run non-streaming); `#1772` double-system-prompt with OpenAIChatClient+Ollama+middleware (verify vs 1.9.0); native client `total_token_count` not set (derive); `prompt_eval_count` historically flaky for cached/large prompts. **No community report exists of MAF *Python* against a *local* model on an *Intel Mac* with token usage — this scenario is under-documented; verify empirically.**
|
||
|
||
## Gemini Second Opinion
|
||
|
||
Unavailable (same MCP-SDK/Interactions-API failure as Topics 1–2). No independent
|
||
Gemini triangulation; treat as absent, not negative.
|
||
|
||
## Synthesis
|
||
|
||
The brief's premise — "run locally via `OpenAIChatClient(base_url=...)`; `UsageDetails`
|
||
works because the local server returns OpenAI-style `usage`" — is *directionally
|
||
right but wrong in two specifics that would fail success criteria on the very
|
||
profile we develop on*. First, the **Chat Completions** client
|
||
(`OpenAIChatCompletionClient`), not the **Responses** client (`OpenAIChatClient`),
|
||
is the one that talks to a local `/v1` endpoint and the one whose verified
|
||
`_parse_usage_from_openai` populates `UsageDetails`. Second, **streaming is the
|
||
trap**: it is exactly where local `/v1` silently drops/leaks tool calls AND where
|
||
`usage` comes back `None` unless `include_usage` is set (uncertain in MAF). Running
|
||
the debate/validator path **non-streaming** fixes both at once — and non-streaming
|
||
is what we want anyway for deterministic accounting.
|
||
|
||
The contrarian's "use the native `OllamaChatClient` instead" is a real option (MS
|
||
docs route you to it; it sidesteps `/v1` translation edges and gets
|
||
`prompt_eval_count`/`eval_count` directly), but it conflicts with the project's
|
||
**GA-pin dependency discipline** (it is a `--pre` beta + a new `ollama` dep). The
|
||
resolution is a **spike-gate**: start with the zero-new-dep Chat-Completions
|
||
`base_url` path non-streaming; if a quick spike shows tool-calls leaking as text, or
|
||
`usage=None`, or the #1772 middleware bug biting, switch to the native client and
|
||
accept the scoped `--pre` dependency. Either way the model-map → chat-client ctor
|
||
abstraction (D2) hides the choice behind the profile.
|
||
|
||
The deeper truth the swarm surfaces: on Intel-Mac CPU, "local primary" means
|
||
**local proves the slice RUNS** (plumbing, tool-calls fire, validator parses, usage
|
||
populated) — it does **not** prove the agents produce *good* candidates. "Both
|
||
profiles exercised" therefore means: local = it runs; one minimal Foundry pass
|
||
(cheapest model, hard cap) = the candidates are substantively sound. And the model's
|
||
tool-calling weakness is absorbed by our **blocking validator-as-retry** — the
|
||
obligatory validator stops being just a gate and becomes the reliability mechanism
|
||
that makes a weak local model usable.
|
||
|
||
## Open Questions
|
||
|
||
- **Concrete local model + endpoint** (brief `[OPEN]`, operator-supplied): default
|
||
to `qwen3:4b` via Ollama on `127.0.0.1:11434` for dev/smoke; `qwen2.5:7b`/`qwen3:8b`
|
||
for reliability checks. The whole free-local run hinges on a working local endpoint
|
||
existing on the machine.
|
||
- **Foundry deployment names** (brief `[OPEN]`, tenant-specific, operator-supplied) —
|
||
back the role→deployment map for the minimal Azure/Foundry-profile check.
|
||
- **base_url vs native client** — resolve via a one-call spike in /trekexecute:
|
||
does `OpenAIChatCompletionClient(base_url)` non-streaming return populated
|
||
`UsageDetails` and structured tool-calls on the chosen model? If yes, no new dep;
|
||
if no, switch to native `OllamaChatClient`.
|
||
- **Does MAF set `include_usage` on streaming?** Unverified — irrelevant if we run
|
||
non-streaming, but confirm if streaming is ever needed for narration.
|
||
|
||
## Recommendation
|
||
|
||
**Run the local profile via `OpenAIChatCompletionClient(base_url=<local /v1>)`,
|
||
non-streaming, behind the D2 model-map abstraction; keep the native `OllamaChatClient`
|
||
as a spike-gated fallback.**
|
||
|
||
1. **Client:** `OpenAIChatCompletionClient(base_url="http://127.0.0.1:11434/v1/",
|
||
api_key="ollama", model_id="qwen3:4b")` — installed, **zero new deps**, honours the
|
||
GA-pin discipline. NOT `OpenAIChatClient` (Responses API).
|
||
2. **Non-streaming for debate + validator** — dodges `/v1` streaming+tools drop AND
|
||
streaming `usage=None` in one move. Streaming only for non-tool narration, if ever.
|
||
3. **Budget middleware: treat `usage_details is None` as a HARD FAIL in dev**
|
||
(assertion) so a usage-reporting regression can never silently disable the budget
|
||
cap. Maps directly to the "real token accounting" success criterion (assert the
|
||
meter is populated from `UsageDetails` on a real run). Accumulate via
|
||
`add_usage_details(...)`. No `len(...split())` proxy in `src/`.
|
||
4. **Validator-as-retry:** the blocking Pydantic validator must retry/repair on parse
|
||
failure — small models emit unparseable/text-leaked tool calls; this turns the
|
||
obligatory validator into the reliability mechanism.
|
||
5. **Model:** `qwen3:4b` for dev/smoke (MAF-blessed, tool-capable, CPU-tolerable);
|
||
`qwen2.5:7b`/`qwen3:8b` for reliability checks; `num_ctx=32k`.
|
||
6. **Determinism golden tests:** pin validator + Monte-Carlo inputs (fixed seed →
|
||
identical decision/percentiles); never assert determinism through the LLM.
|
||
7. **"Both profiles exercised":** local (full run) proves the slice RUNS; one minimal
|
||
Foundry pass (cheapest model, hard token cap, per D6) proves candidates are
|
||
substantively sound. Keep local runs tiny (1 maker + 1 checker, ≤3 rounds).
|
||
8. **Spike-gate the client choice** in /trekexecute: if `base_url` non-streaming shows
|
||
tool-call text-leakage, `usage=None`, or #1772, switch to native `OllamaChatClient`
|
||
(accept the `--pre` `agent-framework-ollama` dep + verify its `prompt_eval_count`/
|
||
`eval_count` mapping; derive `total_token_count`).
|
||
9. **Security hardening:** Ollama bind `127.0.0.1`, pin **≥ 0.17.1**, `OLLAMA_DEBUG`
|
||
unset; document model-pull as explicit egress; dummy `api_key`.
|
||
|
||
Risks to carry into the plan: (a) Intel-CPU latency → local is plumbing/smoke, not
|
||
fast iteration; (b) small-model tool-calling weakness → lean on validator-retry +
|
||
Qwen3; (c) native-client fallback is `--pre` (GA-discipline tension) — adopt only if
|
||
spike-gated.
|
||
|
||
## Sources
|
||
|
||
| # | Source | Type | Quality | Used in |
|
||
|---|--------|------|---------|---------|
|
||
| 1 | `.venv/.../agent_framework_openai/__init__.py:33-43` (`OpenAIChatClient`, `OpenAIChatCompletionClient`) | codebase | high | Dim 1 |
|
||
| 2 | `.venv/.../agent_framework_openai/_chat_completion_client.py:705,724-726,757-776` (UsageDetails mapping, None-safe) | codebase | high | Dim 2 |
|
||
| 3 | `.venv/.../agent_framework/_types.py:402-417,547` (`UsageDetails`, `add_usage_details`, `response.usage_details`) | codebase | high | Dim 2 |
|
||
| 4 | `.venv/.../agent_framework_openai/_chat_client.py:376,382` (`base_url`, `api_key`) | codebase | high | Dim 1 |
|
||
| 5 | `agent_framework/ollama/__init__.pyi` + `import agent_framework_ollama` fails (not installed) | codebase | high | Dim 1, 6 |
|
||
| 6 | <https://learn.microsoft.com/agent-framework/integrations/openai-endpoints> (base_url table, ChatCompletion client) | official | high | Dim 1, 2 |
|
||
| 7 | <https://learn.microsoft.com/agent-framework/agents/providers/ollama> (native client, tool caveat, qwen3:4b) | official | high | Dim 1,3 |
|
||
| 8 | <https://learn.microsoft.com/python/api/agent-framework-core/agent_framework.usagedetails?view=agent-framework-python-latest> | official | high | Dim 2 |
|
||
| 9 | <https://pypi.org/project/agent-framework-ollama/> (1.0.0b260521 beta, --pre) | official | high | Dim 1, 6 |
|
||
| 10 | <https://github.com/microsoft/agent-framework/blob/main/python/packages/ollama/pyproject.toml> (deps: ollama <0.5.4) | official | high | Dim 1, 6 |
|
||
| 11 | <https://docs.ollama.com/api/openai-compatibility> (usage; streaming needs include_usage) | official | high | Dim 2 |
|
||
| 12 | <https://docs.ollama.com/api/usage> (prompt_eval_count/eval_count) | official | high | Dim 2 |
|
||
| 13 | <https://github.com/microsoft/agent-framework/releases> (1.8.0 OTel usage fix; 1.9.0 ollama tools fix) | official | high | Dim 2 |
|
||
| 14 | <https://github.com/microsoft/agent-framework/issues/1772> (double system prompt, Ollama+middleware) | community | medium | Dim 3 |
|
||
| 15 | <https://ollama.com/blog/streaming-tool> (streaming+tools fixed 2025-05-28) | official | high | Dim 3 |
|
||
| 16 | <https://github.com/ollama/ollama/issues/12557> (streaming tool-call regression 0.12.3) | community | medium | Dim 3 |
|
||
| 17 | <https://www.docker.com/blog/local-llm-tool-calling-a-practical-evaluation/> (tool-call F1 by model) | community | high | Dim 3 |
|
||
| 18 | <https://www.morphllm.com/best-ollama-models> (Qwen3 best tool-calling) | community | medium | Dim 3 |
|
||
| 19 | <https://localaimaster.com/blog/ollama-system-requirements> (Intel i9 4-6 tok/s, no MLX) | community | medium | Dim 4 |
|
||
| 20 | <https://github.com/ollama/ollama/issues/586> (seed+temp0 not deterministic) | community | medium | Dim 5 |
|
||
| 21 | <https://www.wiz.io/blog/probllama-ollama-vulnerability-cve-2024-37032> (CVE-2024-37032) | community | high | Dim 6 |
|
||
| 22 | <https://github.com/advisories/GHSA-f6mr-38g8-39rg> (CVE-2025-63389 no-auth by design) | official | high | Dim 6 |
|
||
| 23 | <https://www.runzero.com/blog/ollama/> (CVE-2026-7482 Bleeding Llama, < 0.17.1) | community | high | Dim 6 |
|
||
| 24 | <https://www.indusface.com/blog/exposed-ollama-servers-llm-security-risks/> (bind 127.0.0.1) | community | high | Dim 6 |
|
||
| 25 | <https://learn.microsoft.com/agent-framework/agents/observability> (no native cost metric) | official | high | Best Practice |
|
||
| 26 | <https://lmstudio.ai/app-privacy> (LM Studio local persistence, no telemetry default) | official | medium | Dim 6 |
|