Instrument an existing agent
Retrofit Progress Observability onto an existing codebase with the smallest
possible diff, then run it once so spans start flowing. For a project that
does not exist yet, this is the wrong skill — it only edits what is already
there.
This skill writes code — the instrumentation edits and nothing else. It never
restructures the app, and it never reads the platform back: confirming the traces
arrived is left to /health-check.
1 · Detect — before touching anything
Scan the repo and report what you found, then the diff you intend to make,
before editing:
Language & entry point — pyproject.toml/requirements.txt,
package.json (check "type": "module" — it decides the wiring path),
*.csproj.
LLM SDKs and frameworks in use — imports of openai, anthropic,
langchain, llama_index, @langchain/*, Microsoft.Extensions.AI, etc.
Check each against the supported-instruments list in the language reference.
A client pointed at an OpenAI-compatible endpoint (OpenRouter, LiteLLM,
vLLM, Together, most gateways) counts as OpenAI — see the reference.
A .NET agent built from AIProjectClient (Azure AI Foundry) has no
wrappable chat client — different wiring; see the Foundry section in
references/dotnet.md.
Existing telemetry — OpenTelemetry setup, Traceloop, or a previous
Progress Observability init. Look for it before writing anything, and check
the reference for ordering rather than assuming init belongs first.
Python (measured). The SDK attaches to a provider the app already
installed, so Progress ends up alongside the app's exporter rather than
replacing it — but only if init runs after the app's own setup. Init
first and the app's set_tracer_provider() becomes a no-op with one warning
line, killing its existing telemetry while Progress spans keep arriving.
Grep for set_tracer_provider / TracerProvider(. Details in
references/python.md.
.NET (measured). No ordering constraint: providers coexist and the
app's own tracing is unaffected. The hazard is different — a chat client
that already has .UseOpenTelemetry() records every call twice once
.AddObservability() is added, doubling token and cost figures. Grep for
UseOpenTelemetry before wiring and report the overlap rather than
removing either layer yourself. One hit is not overlap:
UseOpenTelemetry(sourceName: ObservabilityTracer.SourceName) on an
agent builder is Progress wiring itself — leave it. Progress spans land
in their own trace by design; see references/dotnet.md.
TypeScript (measured). The app's provider.register() first, then
instrument(): on OpenTelemetry 2.x both the app's exporter and Progress
receive every span. Init first and the telemetry splits — global-tracer
spans reach Progress only, provider.getTracer() spans reach the app only.
On OpenTelemetry 1.x the recommended order attaches and then silently
exports nothing to Progress (a serializer mismatch, visible only with
debug: true), so check the app's @opentelemetry/* major before
declaring success. Grep for NodeTracerProvider / NodeSDK /
.register(. Details and the Genkit case in references/typescript.md.
Scope — a monorepo, several services, or more than one agent needs a
"which one?" question before you touch anything. Don't pick for the user.
Config style — dotenv, user secrets, plain env — instrumentation config
should arrive the same way the app's other secrets do.
Package manager — lockfiles decide the install command: uv.lock /
poetry.lock / Pipfile for Python, yarn.lock / pnpm-lock.yaml for
Node (defaults: pip / npm); .csproj always means dotnet add package.
Meta package present? (Python) — if the dependency list has
langchain-core or llama-index-core but not the langchain /
llama-index meta package, note it: adding it is a required Wire edit
below. Check the dependency file, not the imports.
If a framework in use is not in the supported list — DSPy, AutoGen,
Pydantic AI, Semantic Kernel and Google ADK are the common ones — say so
plainly and offer the decorator / manual-span fallback from the reference.
Never imply auto-instrumentation covers a framework it doesn't. LangGraph is
supported in Python (it rides the LangChain instrumentor and produces full
graph topology); it is not supported in the JS SDK. Genkit is supported
in the JS SDK from 3.1.0, with an ordering rule (init before the first flow)
and a double-count to block — see the reference. Check the language reference
rather than assuming either way.
2 · Wire — follow the language reference exactly
Open the matching reference and use its snippets verbatim — they are verified
against the published packages, not reconstructed:
references/python.md — progress-observability (PyPI)
references/typescript.md — @progress/observability (npm)
references/dotnet.md — Progress.Observability.Instrumentation (NuGet)
Rules that hold across all three:
Init at process start, before any LLM client exists. In ESM Node that
means the hooks import and Observability.instrument() run before the app is
even imported; in Python, before the LLM and framework imports — not merely
before clients are constructed. An import can bind references that later
patching never reaches, and which imports do that is not classifiable from
the outside, so the default removes the judgment call; # noqa: E402 on
the displaced imports is the accepted cost. In .NET, before the agent is
built. Two exceptions,
both in the language references: an app that installs its own
OpenTelemetry provider (init goes after that), and Haystack (init goes
after import haystack.tracing). Check the reference before assuming
earlier is safer — in both cases it isn't.
Minimal diff. Typically: one dependency, one import, one init call, env
var wiring, and a flush-on-exit. If you find yourself moving app code around,
stop and reconsider — including "just" exporting a module-scope script so you
have something to wrap. That is restructuring, and it is never the answer.
App with no LLM calls: instrument every step, not the entry point alone.
Decorators (Python/.NET) and wrapFunctionWithSpan (TS) are the only span
source in that app, so wrap the entry point and each internal step and
every tool-like callable, on the functions the app already has. One span
around the top is the failure mode to avoid: it produces a clean, plausible
trace that misrepresents a multi-step pipeline as a single unit, and nothing
in the output reveals it. Each reference has the kind-by-kind table. When
auto-instrumentation is doing the work instead, add none of them.
Python + LangChain or LlamaIndex: the meta package is a REQUIRED edit.
langchain-core / llama-index-core alone leave the instrumentor off: the
app runs, LLM spans arrive, and no structure is ever emitted — silently.
(Measured: 2 spans → 8 for LangChain, 1 → 17 for LlamaIndex, from that one
line.) Add langchain / llama-index to the dependency file alongside the
-core package, never in place of it — the app imports langchain_core /
llama_index.core by name, so those stay declared. Say in your report that
you added it and why; gate table in references/python.md.
TypeScript + LangChain: @langchain/core declared is a REQUIRED edit.
The hierarchy patch keys off @langchain/core appearing in the app's own
package.json; an app that declares only @langchain/openai gets a lone
chat span per call and no chain structure, silently (measured). Add it
alongside, never in place of, what the app already declares.
Never ask the user to paste a key into the chat. Reference the env var
or config entry by name and let them set it themselves, in their own shell,
.env, or secret store. Read config to detect what exists; never echo a
secret's value back.
A missing key must be loud. In Python and TS, read it so an unset
variable raises (os.environ["OBSERVABILITY_API_KEY"], not .get(...)).
In .NET, follow the reference's optional-tracing pattern instead: skip init
with a prominent warning and keep the app running. Either way, the one
forbidden outcome is silence — an app that runs, exports nothing, and gives
no clue why.
Keys via config, never hardcoded. The key here is the Integration key
(ac_p_…) from Progress Observability → API Keys. It is not the MCP key
(acm_…) — that one belongs to the coding agent's environment for the
verify step, not to the app.
Declaring the dependency is a file edit, and it is never optional. Add the
SDK to package.json / requirements.txt / pyproject.toml / .csproj
yourself, creating the dependencies section if the manifest has none. Do
this even when you have been told not to run installs, and even when you
cannot run them — an import of a package the manifest doesn't declare is
ERR_MODULE_NOT_FOUND the moment anyone runs the app. Telling the user to
run npm install … afterwards is not a substitute for the edit; running
the installer is the separate, optional half.
Check before adding. If the SDK is already in the manifest, don't re-add
it — note its version and move on (suggest an update only if it's older than
the reference's verified version). Otherwise add the latest, through the
project's own package manager where you can run it (each reference lists
the commands), after a quick registry check of the current version — if the
registry is ahead of the reference's "verified against" version, say so in
your report rather than assuming the reference still holds.
app_name is the platform identity — a short stable slug. Everything in
the platform (and every other skill in this plugin) filters by it, so confirm
it with the user. Read it from the environment with the agreed name as the
fallback, rather than hardcoding it outright:
app_name=os.environ.get("OBSERVABILITY_APP_NAME", "my-app")
Same shape in TS (process.env.OBSERVABILITY_APP_NAME ?? 'my-app') and .NET.
The literal still documents the intent, but the same build can then report as
a different service per environment — dev, staging, prod, CI — with no code
change. Hardcoding it means every environment lands in one bucket.
Add a variable; don't repurpose an existing one. Where the app already
names itself for its own reasons — an agent's name:, a service
registration, a CLI banner — leave that alone and introduce a separate
app_name. Pointing an existing field at your new env var makes the app's
behavior change with a telemetry setting, which is app logic edited for an
instrumentation task.
Content capture default is ON. Prompts/completions are sent unless
content tracing is disabled. For apps handling sensitive data, offer the
content off-switch (each reference shows it) — metadata keeps flowing.
3 · Run once
Have the user run the app so it emits at least one trace (run it yourself if it
is runnable here). If LLM credentials aren't available, the decorator/manual
span path in each reference produces real spans with no LLM call — wire one
workflow-decorated function and run that, so the pipeline can be proven
end-to-end before the model keys exist.
4 · Confirm & hand off — no platform read
Instrumentation is finished once the edits are in and the app has emitted at
least one trace. This skill does not read back over MCP. Confirming that the
spans actually landed is a separate, read-only step the user runs when they want
it — never something this skill does automatically. Do not call
list_observations, get_observation_details, or any other platform tool here.
Report what you already know from the edits themselves — the language, the
framework, and whether auto-instrumentation or manual spans are carrying the
trace — then tell the user plainly that wiring is done, and hand off with where
to confirm the traces:
Wiring is complete. Run your agent so it produces some traffic, then open
observability.progress.com and confirm the traces are flowing in for
service <app_name> — they should appear within a minute of the run.
An unverified wiring is a normal, healthy outcome — especially for a new user on
the free tier, who has no MCP key to read traces with. Treating it as a failure
is a bad first experience for exactly the people most likely to be trying the
product for the first time.
Never touch the platform from this skill — no reads, no writes. The only changes
it makes are the local instrumentation edits.
1---2name: instrument-agent3description: Add Progress Observability instrumentation to an existing AI agent or LLM app — Python, TypeScript/JavaScript, or .NET, including LangChain, LangGraph, LlamaIndex, CrewAI, OpenAI Agents, Haystack, MCP servers and Microsoft.Extensions.AI — with the smallest possible diff, then hand off with where to confirm the traces. Use when the user asks to "instrument my agent", "add observability", "add tracing/telemetry to this repo", "connect this to Progress Observability", or has an existing uninstrumented project they want on the platform. Not for creating a project from scratch — this only edits code that already exists.4license: MIT5---67# Instrument an existing agent89Retrofit Progress Observability onto an existing codebase with the smallest10possible diff, then run it once so spans start flowing. For a project that11does not exist yet, this is the wrong skill — it only edits what is already12there.1314**This skill writes code** — the instrumentation edits and nothing else. It never15restructures the app, and it never reads the platform back: confirming the traces16arrived is left to `/health-check`.1718<!-- copilot:start -->19## 1 · Detect — before touching anything2021Scan the repo and report what you found, then the diff you intend to make,22*before* editing:2324- **Language & entry point** — `pyproject.toml`/`requirements.txt`,25 `package.json` (check `"type": "module"` — it decides the wiring path),26 `*.csproj`.27- **LLM SDKs and frameworks in use** — imports of `openai`, `anthropic`,28 `langchain`, `llama_index`, `@langchain/*`, `Microsoft.Extensions.AI`, etc.29 Check each against the supported-instruments list in the language reference.30 A client pointed at an **OpenAI-compatible endpoint** (OpenRouter, LiteLLM,31 vLLM, Together, most gateways) counts as OpenAI — see the reference.32 A .NET agent built from `AIProjectClient` (Azure AI Foundry) has no33 wrappable chat client — different wiring; see the Foundry section in34 `references/dotnet.md`.35- **Existing telemetry** — OpenTelemetry setup, Traceloop, or a previous36 Progress Observability init. Look for it before writing anything, and check37 the reference for ordering rather than assuming init belongs first.3839 **Python (measured).** The SDK attaches to a provider the app already40 installed, so Progress ends up alongside the app's exporter rather than41 replacing it — **but only if init runs after the app's own setup**. Init42 first and the app's `set_tracer_provider()` becomes a no-op with one warning43 line, killing its existing telemetry while Progress spans keep arriving.44 Grep for `set_tracer_provider` / `TracerProvider(`. Details in45 `references/python.md`.4647 **.NET (measured).** No ordering constraint: providers coexist and the48 app's own tracing is unaffected. The hazard is different — a chat client49 that already has `.UseOpenTelemetry()` records every call twice once50 `.AddObservability()` is added, doubling token and cost figures. Grep for51 `UseOpenTelemetry` before wiring and report the overlap rather than52 removing either layer yourself. One hit is not overlap:53 `UseOpenTelemetry(sourceName: ObservabilityTracer.SourceName)` on an54 *agent* builder is Progress wiring itself — leave it. Progress spans land55 in their own trace by design; see `references/dotnet.md`.5657 **TypeScript (measured).** The app's `provider.register()` first, then58 `instrument()`: on OpenTelemetry 2.x both the app's exporter and Progress59 receive every span. Init first and the telemetry splits — global-tracer60 spans reach Progress only, `provider.getTracer()` spans reach the app only.61 **On OpenTelemetry 1.x the recommended order attaches and then silently62 exports nothing to Progress** (a serializer mismatch, visible only with63 `debug: true`), so check the app's `@opentelemetry/*` major before64 declaring success. Grep for `NodeTracerProvider` / `NodeSDK` /65 `.register(`. Details and the Genkit case in `references/typescript.md`.66- **Scope** — a monorepo, several services, or more than one agent needs a67 "which one?" question before you touch anything. Don't pick for the user.68- **Config style** — dotenv, user secrets, plain env — instrumentation config69 should arrive the same way the app's other secrets do.70- **Package manager** — lockfiles decide the install command: `uv.lock` /71 `poetry.lock` / `Pipfile` for Python, `yarn.lock` / `pnpm-lock.yaml` for72 Node (defaults: `pip` / `npm`); `.csproj` always means `dotnet add package`.73- **Meta package present? (Python)** — if the dependency list has74 `langchain-core` or `llama-index-core` but not the `langchain` /75 `llama-index` meta package, note it: adding it is a required Wire edit76 below. Check the dependency file, not the imports.7778If a framework in use is *not* in the supported list — DSPy, AutoGen,79Pydantic AI, Semantic Kernel and Google ADK are the common ones — say so80plainly and offer the decorator / manual-span fallback from the reference.81Never imply auto-instrumentation covers a framework it doesn't. **LangGraph is82supported** in Python (it rides the LangChain instrumentor and produces full83graph topology); it is *not* supported in the JS SDK. **Genkit is supported84in the JS SDK from 3.1.0**, with an ordering rule (init before the first flow)85and a double-count to block — see the reference. Check the language reference86rather than assuming either way.8788## 2 · Wire — follow the language reference exactly8990Open the matching reference and use its snippets verbatim — they are verified91against the published packages, not reconstructed:9293- `references/python.md` — `progress-observability` (PyPI)94- `references/typescript.md` — `@progress/observability` (npm)95- `references/dotnet.md` — `Progress.Observability.Instrumentation` (NuGet)9697Rules that hold across all three:9899- **Init at process start, before any LLM client exists.** In ESM Node that100 means the hooks import and `Observability.instrument()` run before the app is101 even imported; in Python, before the LLM and framework imports — not merely102 before clients are constructed. An import can bind references that later103 patching never reaches, and which imports do that is not classifiable from104 the outside, so the default removes the judgment call; `# noqa: E402` on105 the displaced imports is the accepted cost. In .NET, before the agent is106 built. **Two exceptions,107 both in the language references:** an app that installs its own108 OpenTelemetry provider (init goes after that), and Haystack (init goes109 after `import haystack.tracing`). Check the reference before assuming110 earlier is safer — in both cases it isn't.111- **Minimal diff.** Typically: one dependency, one import, one init call, env112 var wiring, and a flush-on-exit. If you find yourself moving app code around,113 stop and reconsider — including "just" exporting a module-scope script so you114 have something to wrap. That is restructuring, and it is never the answer.115- **App with no LLM calls: instrument every step, not the entry point alone.**116 Decorators (Python/.NET) and `wrapFunctionWithSpan` (TS) are the *only* span117 source in that app, so wrap the entry point **and** each internal step **and**118 every tool-like callable, on the functions the app already has. One span119 around the top is the failure mode to avoid: it produces a clean, plausible120 trace that misrepresents a multi-step pipeline as a single unit, and nothing121 in the output reveals it. Each reference has the kind-by-kind table. When122 auto-instrumentation is doing the work instead, add none of them.123- **Python + LangChain or LlamaIndex: the meta package is a REQUIRED edit.**124 `langchain-core` / `llama-index-core` alone leave the instrumentor off: the125 app runs, LLM spans arrive, and no structure is ever emitted — silently.126 (Measured: 2 spans → 8 for LangChain, 1 → 17 for LlamaIndex, from that one127 line.) Add `langchain` / `llama-index` to the dependency file **alongside the128 `-core` package, never in place of it** — the app imports `langchain_core` /129 `llama_index.core` by name, so those stay declared. Say in your report that130 you added it and why; gate table in `references/python.md`.131- **TypeScript + LangChain: `@langchain/core` declared is a REQUIRED edit.**132 The hierarchy patch keys off `@langchain/core` appearing in the app's own133 `package.json`; an app that declares only `@langchain/openai` gets a lone134 `chat` span per call and no chain structure, silently (measured). Add it135 alongside, never in place of, what the app already declares.136- **Never ask the user to paste a key into the chat.** Reference the env var137 or config entry by name and let them set it themselves, in their own shell,138 `.env`, or secret store. Read config to detect what exists; never echo a139 secret's value back.140- **A missing key must be loud.** In Python and TS, read it so an unset141 variable raises (`os.environ["OBSERVABILITY_API_KEY"]`, not `.get(...)`).142 In .NET, follow the reference's optional-tracing pattern instead: skip init143 with a **prominent warning** and keep the app running. Either way, the one144 forbidden outcome is silence — an app that runs, exports nothing, and gives145 no clue why.146- **Keys via config, never hardcoded.** The key here is the **Integration** key147 (`ac_p_…`) from Progress Observability → API Keys. It is not the MCP key148 (`acm_…`) — that one belongs to the *coding agent's* environment for the149 verify step, not to the app.150- **Declaring the dependency is a file edit, and it is never optional.** Add the151 SDK to `package.json` / `requirements.txt` / `pyproject.toml` / `.csproj`152 yourself, creating the `dependencies` section if the manifest has none. Do153 this even when you have been told not to run installs, and even when you154 cannot run them — an import of a package the manifest doesn't declare is155 `ERR_MODULE_NOT_FOUND` the moment anyone runs the app. Telling the user to156 run `npm install …` afterwards is **not** a substitute for the edit; running157 the installer is the separate, optional half.158- **Check before adding.** If the SDK is already in the manifest, don't re-add159 it — note its version and move on (suggest an update only if it's older than160 the reference's verified version). Otherwise add the latest, through the161 project's *own* package manager where you can run it (each reference lists162 the commands), after a quick registry check of the current version — if the163 registry is ahead of the reference's "verified against" version, say so in164 your report rather than assuming the reference still holds.165- **`app_name` is the platform identity** — a short stable slug. Everything in166 the platform (and every other skill in this plugin) filters by it, so confirm167 it with the user. **Read it from the environment with the agreed name as the168 fallback**, rather than hardcoding it outright:169170 ```python171 app_name=os.environ.get("OBSERVABILITY_APP_NAME", "my-app")172 ```173174 Same shape in TS (`process.env.OBSERVABILITY_APP_NAME ?? 'my-app'`) and .NET.175 The literal still documents the intent, but the same build can then report as176 a different service per environment — dev, staging, prod, CI — with no code177 change. Hardcoding it means every environment lands in one bucket.178179 **Add a variable; don't repurpose an existing one.** Where the app already180 names itself for its own reasons — an agent's `name:`, a service181 registration, a CLI banner — leave that alone and introduce a separate182 `app_name`. Pointing an existing field at your new env var makes the app's183 behavior change with a telemetry setting, which is app logic edited for an184 instrumentation task.185- **Content capture default is ON.** Prompts/completions are sent unless186 content tracing is disabled. For apps handling sensitive data, offer the187 content off-switch (each reference shows it) — metadata keeps flowing.188189## 3 · Run once190191Have the user run the app so it emits at least one trace (run it yourself if it192is runnable here). If LLM credentials aren't available, the decorator/manual193span path in each reference produces real spans with **no** LLM call — wire one194`workflow`-decorated function and run that, so the pipeline can be proven195end-to-end before the model keys exist.196197## 4 · Confirm & hand off — no platform read198199Instrumentation is finished once the edits are in and the app has emitted at200least one trace. **This skill does not read back over MCP.** Confirming that the201spans actually landed is a separate, read-only step the user runs when they want202it — never something this skill does automatically. Do **not** call203`list_observations`, `get_observation_details`, or any other platform tool here.204205Report what you already know from the edits themselves — the language, the206framework, and whether auto-instrumentation or manual spans are carrying the207trace — then tell the user plainly that wiring is done, and hand off with where208to confirm the traces:209210> Wiring is complete. Run your agent so it produces some traffic, then open211> **observability.progress.com** and confirm the traces are flowing in for212> service `<app_name>` — they should appear within a minute of the run.213214An unverified wiring is a normal, healthy outcome — especially for a new user on215the free tier, who has no MCP key to read traces with. Treating it as a failure216is a bad first experience for exactly the people most likely to be trying the217product for the first time.218219Never touch the platform from this skill — no reads, no writes. The only changes220it makes are the local instrumentation edits.221<!-- copilot:end -->