Instrument with coding agents
Copy one prompt into the coding agent you already use and let it add Neatlogs tracing to your repository.
Use the coding agent you already work in to add Neatlogs. Copy the prompt below and run it at the root of your repository in Claude Code, Codex, Cursor, GitHub Copilot, Windsurf, OpenCode, or any agent that can edit files. The agent detects your language and frameworks, installs the matching Neatlogs SDK (or sets up direct HTTP or OpenTelemetry export for languages without one), and adds only tracing.
Set up
- Get your project API key. Sign in to the dashboard for your project (US or EU), select the project, and copy its key from Settings → Project → API keys. Set it as
NEATLOGS_API_KEYin the shell or.envfile your application uses, and don't commit it. - Open your coding agent at the repository root. The prompt only instruments services in or below the directory the agent runs in.
- Copy the prompt below and paste it into the agent.
- Review the changes. Expect SDK installation, a single initialization near the app's entry point, and tracing wrappers or spans. The prompt tells the agent not to change application logic or set up anything beyond tracing.
- Run one real entry point and confirm the trace. The agent does not run your application. Trigger a representative request yourself, then open Traces in the dashboard and check that the run appears with its model and tool steps.
The prompt
# Trace an AI agent with Neatlogs
Set up Neatlogs tracing in this working directory. Follow these steps in order.
## Step 1: Set up Neatlogs access
Determine whether this is a US or EU project from the dashboard the user uses. If the region is unknown, ask the user to choose; do not infer it from the API key. Check whether `NEATLOGS_API_KEY` is set in the environment. If it is, skip only the key setup below.
If it isn't, ask the user to do the following, then stop and wait for the user to confirm. Do not guess, invent, or hardcode an API key.
1. Sign up or sign in to the dashboard for the project you want to instrument: https://app.neatlogs.com for US or https://eu.app.neatlogs.com for EU
2. With the right project active, go to **Settings → Project → API keys** in the dashboard and copy its key
3. Set `NEATLOGS_API_KEY` as an environment variable in the shell the user will run the app from, for example `export NEATLOGS_API_KEY="..."`. If the app loads a `.env` file, add it there instead. The user should not commit the key to version control.
This is the SDK project key for ingestion and SDK Doctor. It is not a public API CLI credential; CLI reads require separate OAuth login or a service-account token.
Once the user confirms, continue. If the key isn't visible in your own environment, that's expected when the user set it in a separate shell or in a `.env` file that only the app loads. Don't block on it.
## Step 2: Detect the language and stack
Look at the workspace and determine the language and which LLM providers / agent frameworks it uses. If the project spans more than one language, instrument each one. If the project is a monorepo, check the git root to get oriented, but only ever instrument services in or below the current working directory - never a sibling or parent app outside it, even if a workspace/compose config references one.
**Finding independent services.** A monorepo can bundle several independently-deployable services; each one that reaches an LLM/agent/tool call gets treated as its own entry point later (own workflow name in Step 4) - skip health/readiness/CRUD-only services that never call a model entirely. Check these signals in order and stop at the first one that finds more than one service directory:
1. **Workspace config** (most authoritative) - a `workspaces` field in the root `package.json`, or a `packages` field in `pnpm-workspace.yaml`. Resolve those glob patterns to directories, then keep only the ones that actually have their own manifest (`package.json`, `pyproject.toml`, `go.mod`, etc.) - a glob can match a plain non-package directory too.
2. **docker-compose** - `docker-compose.yml`/`docker-compose.yaml`/`compose.yml`/`compose.yaml` at the root. Each entry under `services:` whose `build` (a plain string, or an object's `context` field) resolves to an existing directory is one service.
3. **Scattered manifests** (fallback, only if neither signal above found more than one) - search for manifest files (`package.json`, `pyproject.toml`, `go.mod`, etc.) up to 4 directories deep, excluding `node_modules`, `.git`, `dist`, `build`, `.next`, `venv`, `.venv`, `__pycache__`, `target`, `.turbo`, `vendor`. Drop the repo root's own manifest (that's the whole-repo language, not a separate service) and drop any candidate directory that's nested inside another candidate - a service's own internal shared-lib subpackage is not a separate service.
Edge case: if exactly one of these signals finds exactly one nested service, and the repo root itself has no manifest/language of its own, use that single nested directory as the actual service root instead of the top-level directory - it IS the real service, just not living at the repo root (e.g. a `services/api/` with nothing else at the top level).
If none of the three signals finds more than one candidate, treat the whole repo at the current working directory as a single service - this is the common case, and nothing else here changes for it. For every service found this way, only keep it if it reaches an LLM/agent/tool call - drop the rest, per the CRUD/health-check exclusion above.
**Identifying entry points within each service.** A single service usually has several independent, separately-triggerable capabilities - these are the actual entry points, not the service as a whole. Typical shapes: an HTTP route, a CLI command/subcommand, a cron/scheduled job, a queue/stream consumer (Kafka/SQS/Celery/RabbitMQ/BullMQ - one entry point per message/task type, not one for the whole consumer loop), a webhook/event handler, an agent-loop iteration, or a public library API function. Two things commonly get this wrong:
- **Altitude** - an internal helper the entry point calls into (`run_pipeline`, `process_document`, `process_chunk`) is NOT itself an entry point; the entry point is the outer route/command/job that calls it.
- **Multiplexed entry** - if one transport dispatches to several different capabilities (a single `/run` route branching on a `task_type` field, a Celery task branching by job kind, an argparse dispatcher, a Kafka consumer loop branching on `msg.topic`/`event_type`), treat EACH BRANCH as its own entry point - never the shared dispatcher as one entry point covering all of them.
For each entry point, also note its process kind - it changes how you initialize and where spans root:
- **One-shot script**: initialize once near the top, and end by flushing/shutting down the SDK (its process exits immediately after, and unflushed spans batched in memory are otherwise lost).
- **Long-running server**: initialize once at process startup - never inside a request handler - and only flush/shut down on process shutdown, not per request. Root each request under its own span (e.g. a `WORKFLOW` on the handler) so concurrent requests don't share one trace.
- **Concurrent operations within one entry point** (goroutines, worker threads, manual thread pools - not plain `async`/`await` in the same coroutine, which propagates context on its own): context does not always propagate automatically across a new thread or goroutine. Check Step 3's reference for whether this language/runtime requires threading the trace context through explicitly (Go always does) or handles it for you.
Neatlogs has a native SDK for Python, TypeScript, and Go - if the detected language is one of those, continue to Step 3. For any OTHER language, there is no Neatlogs SDK; pick ONE of these two paths instead, and skip Step 3 (its SDK integration-mechanism guidance is not applicable here):
- **Default: the dependency-free HTTP endpoint.** Use this unless the app already has OpenTelemetry, or the language's OTel support is clearly the more idiomatic fit (e.g. Java, .NET). Follow https://docs.neatlogs.com/sdk/http-injection exactly. The mechanism is fundamentally different from Steps 3–4 below: build ONE nested JSON tree (an object with a `children` array) as the code for one entry-point invocation runs, then send it as a SINGLE `POST /v1/trace` when that invocation completes - success or error, so wrap the send in this language's try/finally-equivalent. Send US projects to `https://ingest.neatlogs.com/v1/trace` and EU projects to `https://eu.ingest.neatlogs.com/v1/trace`. Never POST once per span; that violates the endpoint's contract and produces disconnected traces instead of one.
- **OpenTelemetry / OTLP-gRPC, when the app already emits OpenTelemetry spans** (its own `tracer.start_span()`-style setup, an OTel Collector, or a GenAI-instrumented framework) **or you're setting one up now:** if there's no existing OTel SDK for this language yet, find and install the official one from https://opentelemetry.io/docs/languages/ (its own site - Neatlogs doesn't ship or document per-language OTel setup beyond Python/TS/Go) and add a standard gRPC trace exporter. Either way, point the exporter at Neatlogs: endpoint `ingest.neatlogs.com:443`, gRPC protocol, and the project key sent as the `x-api-key` gRPC metadata key - NOT an `Authorization: Bearer` header. Standard: emit spans following the official OpenTelemetry GenAI semantic conventions (https://opentelemetry.io/docs/specs/semconv/gen-ai/) - `gen_ai.system`, `gen_ai.request.model`, `gen_ai.operation.name`, token/usage attributes, etc. (If this is a TypeScript app using the Vercel AI SDK directly rather than the Neatlogs wrapper, v6's `experimental_telemetry` emits the native `ai.*` attribute format while v7's `telemetry` adapter emits OpenTelemetry GenAI attributes; both shapes are recognized directly.) Spans that don't follow either supported shape still get ingested, but arrive as generic, unparsed spans - no model name, no token counts, no LLM-specific rendering in the dashboard. Full transport detail (including the "flush before the process exits or batched spans are silently dropped" footgun): https://docs.neatlogs.com/sdk/opentelemetry.
For an EU project using OpenTelemetry, use `eu.ingest.neatlogs.com:443` as the OTLP/gRPC exporter endpoint instead of the US endpoint above. Keep the project's EU API key as the `x-api-key` metadata value.
## Step 3: Follow the SDK's instrumentation reference
Python, TypeScript, or Go only - skip this step if you took one of the two paths above instead.
For each language, read the matching page and follow it exactly for HOW to capture LLM/framework calls - it lists, per provider and per framework, whether that one needs a wrapper, Python auto-instrumentation, or a dedicated integration helper, and that list changes as the SDK evolves, so don't guess or reuse a pattern from a different provider.
| Language | Guide |
| --- | ------|
| Python | https://docs.neatlogs.com/sdk/python |
| TypeScript | https://docs.neatlogs.com/sdk/typescript |
| Go | https://docs.neatlogs.com/sdk/go |
## Step 4: Install and initialize the SDK
Python, TypeScript, or Go only - if you took the OpenTelemetry or HTTP-injection path in Step 2, follow that page's own setup instructions instead of this step.
For an EU project, set the SDK init endpoint to `https://eu.ingest.neatlogs.com` (`endpoint=` in Python, `endpoint:` in TypeScript, or `Endpoint:` in Go). Use the EU project key from `https://eu.app.neatlogs.com`.
- Install the SDK using this language's own package manager (`pip`/`poetry` for Python, `npm`/`pnpm`/`yarn` for TypeScript, `go get` for Go - see Step 3's reference for the exact package/module name). Look up and install the latest version rather than pinning to a specific one. Verify the install succeeded.
- Before editing instrumentation, run the installed same-language SDK's local Doctor as documented at https://docs.neatlogs.com/sdk/doctor. It is network-free and needs no project key; do not substitute the public API CLI for SDK Doctor.
- Initialize the SDK once, near the app's entry point - the exact call signature (parameter names, casing, whether the API key is passed explicitly or read from the environment automatically) differs by language, so use Step 3's reference for the precise syntax rather than guessing from another language's example. Whatever the signature, never hardcode the API key. Set the workflow name to describe the specific task or pipeline this process runs - e.g. `invoice-ocr`, `support-agent`, `checkout-flow` - not the repo or app name; the project (selected by the API key) already identifies the app. If the repo has multiple independent entry points that each reach an LLM/agent/tool, give each its own workflow name describing what THAT one does.
- Calling `init()` again with the same configuration is idempotent; calling it with conflicting configuration raises a configuration error until the current SDK generation is shut down. Never add a second `init()` to switch projects inside one running process. Almost every independent entry point you find stays on the same project and is distinguished by its own workflow root. Use a secondary client only when telemetry genuinely belongs to a different Neatlogs project (for example, explicit multi-tenant routing), and confirm that ownership decision with the user rather than inferring it from code structure.
- Capture each LLM/framework call using the mechanism Step 3's reference says to use for that specific provider or framework - do not wrap something the guide says to instrument a different way, and never apply two mechanisms to the same call (e.g. `wrap()` a client AND also list it in `instrumentations=[...]`).
- `wrap()`/`instrumentations=[...]`/framework integrations only capture library calls. Add a manual `@neatlogs.span(kind="WORKFLOW")` / `trace()` around a feature entry point only when you need to GROUP several of those calls under one named trace - not as an alternative way to capture a single call.
## Step 5: Verify instrumentation and ingestion
Follow https://docs.neatlogs.com/sdk/doctor and the language guide to run the installed SDK's `doctor --local` without a credential, then `doctor --probe` with the SDK project key and regional ingest endpoint. These are SDK commands, not subcommands of `neatlogs-cli`. Do not guess the Python, TypeScript, or Go invocation. For HTTP/OTLP-only integrations, follow their guide's verification path instead.
Do not run the application yourself. Ask the user to run an instrumented entry point and provide its trace ID or result. A synthetic Doctor probe verifies the telemetry pipeline; report it separately from verification of the user's actual application trace. Do not claim application instrumentation passed based only on a synthetic probe. With only the SDK project key, use Doctor readback and mark CLI readback "waiting on a human".
## Step 6: Optional public API CLI readback
Install `neatlogs-cli` using `npm install --global neatlogs-cli@0.2.1` and check `neatlogs --version` (Node.js 22 or 24). The SDK key must remain available to the application, but it must not override CLI authentication.
These instructions target 0.2.1. Check `npm view neatlogs-cli@0.2.1 version` before installing; do not claim the new version was installed if unavailable.
Set `NEATLOGS_CLI_HOST` to `https://app.neatlogs.com` for US or `https://eu.app.neatlogs.com` for EU. For EU, replace the `neatlogs-us` profile below with `neatlogs-eu`.
Use the dashboard-specific profile `neatlogs-us` consistently for login and every read. Before signing in, inspect `neatlogs --host "$NEATLOGS_CLI_HOST" profile list`. Reuse this name only if it is unused or already belongs to this exact dashboard origin; otherwise choose an unused name and replace it in every command below. Login creates the profile when needed. Do not rebind an authenticated profile from another dashboard or rely on the default/active profile.
For human OAuth in 0.2.1, use `--credential-source profile` to keep SDK/environment credentials from overriding the saved login. On 0.2.0, omit that flag and prefix each OAuth command with `env -u NEATLOGS_API_KEY -u NEATLOGS_TOKEN` in a POSIX shell, or use equivalent per-process isolation in other shells. Keep the SDK key available to the application.
```bash
neatlogs --host "$NEATLOGS_CLI_HOST" --profile neatlogs-us --credential-source profile auth login --device
# The user must approve the displayed code in their browser.
env -u NEATLOGS_PROJECT_ID neatlogs --host "$NEATLOGS_CLI_HOST" --profile neatlogs-us --credential-source profile projects list
neatlogs --host "$NEATLOGS_CLI_HOST" --profile neatlogs-us --credential-source profile --project '<project-uuid>' whoami
neatlogs --host "$NEATLOGS_CLI_HOST" --profile neatlogs-us --credential-source profile --project '<project-uuid>' traces list --limit 10
neatlogs --host "$NEATLOGS_CLI_HOST" --profile neatlogs-us --credential-source profile --project '<project-uuid>' traces get '<trace-id>'
```
Confirm the project UUID against `projects list`; discovery must not receive `--project` or `NEATLOGS_PROJECT_ID`. A service-account token supplied securely via `NEATLOGS_TOKEN` can replace interactive login: skip login and replace `--credential-source profile` with `--credential-source token` on discovery and read commands. On 0.2.0, omit the credential-source flag and exclude only `NEATLOGS_API_KEY` from each service-token invocation. It needs `context:read` for discovery/whoami and `observability:read` for trace reads, plus access to the selected project. Never ask the user to paste tokens into chat.
Use the project-scoped `whoami` and actual trace read as the authoritative check. Older CLI versions can report `auth status` as authenticated just because a key is present. The command is `traces get`, not `trace get`. Trace detail returns status, token totals and span counts; use `traces spans list <trace-id>` with the same host/profile/project/credential setup for individual spans. A nonexistent trace should return `NOT_FOUND`.
## Rules
- Add only tracing. Do not set up detections, alerts, evals, or anything else.
- Make only the code changes needed to add tracing. Do not run the application yourself, and do not break or meaningfully modify existing code.
- Do not use the Neatlogs wizard CLI (`npx @neatlogs/wizard@latest`) - that's a separate, more thorough path; this prompt is the manual alternative for when it isn't available.
Full documentation index: https://docs.neatlogs.com/llms.txt
If any framework detected in Step 2 has a dedicated skill at https://github.com/neatlogs/skills (check that repo's `skill-menu.json` for the matching package, e.g. a CrewAI or LangChain project), fetch and read it before instrumenting that framework - it carries gotcha-level detail Step 3's reference doesn't (e.g. exact span-double-firing traps, per-framework attachment rules) and is worth the extra step for that framework's correctness.This sets up access to Neatlogs, then uses your coding agent to inspect the project, install the SDK, and configure tracing by hand. No npx @neatlogs/wizard@latest run is required.
Optional next steps: install Agent Skills so your agent keeps instrumenting new code correctly, and connect the MCP server so it can query your traces.
