Developer tools & API

Coding agents

Telmoni's plugins for Claude Code, Codex, OpenCode and Cursor: each turn of the coding agent you already run, recorded as a run, metadata only, with no code.

The coding-agent plugins record each turn of a coding agent you already run as a run in Telmoni, with no code: the models it called and their tokens, the tools it used and the subagents it started, each with its times and whether it failed. They never send a prompt, a reply, a thought, a tool's input or output, a command, a file's path or the folder you work in: a local model named by the path to its file is sent by the file's name alone.

AgentPluginPackageRuns as
Claude Codetelmoni/claude-observability-plugin@telmoni/claude-observability-pluginclaude-code
Codextelmoni/codex-observability-plugin@telmoni/codex-observability-plugincodex
OpenCodetelmoni/opencode-observability-plugin@telmoni/opencode-observability-pluginopencode
Cursortelmoni/cursor-observability-plugin@telmoni/cursor-observability-plugincursor

Each plugin is built on the TypeScript SDK, which it carries inside its own package, and sends what the SDKs send, under a project API key holding only the ingest scope (Send spans). A self-hosted Telmoni's machine host is read as the SDKs read one: given without a scheme, it is https://, or http:// for this machine, the only host plain HTTP goes to. The Claude Code, Codex and Cursor plugins need Node.js 22.23.4, 24.22.1, 26.12.1 or later, October 2026's security releases; Codex's and Cursor's install with npm, so it must be on your PATH, and Claude Code and Codex need git, with which they add a plugin's marketplace; the OpenCode plugin runs inside OpenCode, which installs it itself. Each installs as built, as below; to run one from a checkout instead, build it in its repository:

git clone https://github.com/telmoni/claude-observability-plugin.git   # or the codex-, opencode- or cursor-observability-plugin
(cd claude-observability-plugin && npm install && npm run build)

What a turn records

A turn — from your prompt to the agent's last reply, or to your interruption — is one run of the agent, and your session (Cursor's conversation) is the run's session (gen_ai.conversation.id), so the console shows a session as a session of traces. Inside the run:

  • the model's work, a model call with its model, its provider and its token counts, the cache's reads and writes among them, counted as the GenAI conventions count them: a call per reply for Claude Code, Codex and OpenCode, and one for the whole turn for Cursor, which gives its tokens a turn at a time;
  • each tool call, by its name, with its time, failed where the tool failed and cancelled where it was stopped or never answered (Cursor's plugin hears of a call only once it has ended, so there one that never ended is not recorded);
  • each subagent, a step named by its kind (subagent Explore), holding its own model and tool calls, but for Cursor's, whose calls Cursor runs under a conversation of their own; a Codex subagent's turns are instead runs of their own, in the same session;
  • for Claude Code, the skills the turn ran, by name, as the tag skills.

An interrupted turn is a cancelled run, and a turn an error ended a failed one, named by the error's kind where the agent gives one (Claude Code's rate_limit, Codex's context_window_exceeded; Cursor gives none, so its run fails as AgentError), never its message. A turn is sent once it has ended. If Telmoni cannot be reached or is busy, if its database fails it (500 /errors/internal/database), or if it refuses the key or the endpoint, the turn is sent again as the session's next turn ends, the session's later turns waiting for it. One Telmoni took in part, or refused for good — for what it holds (400, 413), for a paused organization (402) or for any other fault of its own (500) — is logged and not sent again. OpenCode's plugin holds turns not yet sent in memory and sends them every five minutes and as any session's next turn ends (on OpenCode 1, any session's in the same folder), so a turn still unsent three seconds into OpenCode's shutdown is lost; and it reads its key only as OpenCode loads it, so a turn refused for its key is not sent again either. Where Telmoni reaches its Redis, a copy that arrives within the hour of the first is stored once: its run's id is derived from the session and the turn, and every span's from the run's.

What is never read. Of what an agent writes down, a plugin reads only names, ids, times, counts and flags: each entry's kind, id, time and outcome, a reply's model, provider and token counts, a tool call's name, and a subagent's kind. The Claude Code plugin also reads the skill a Skill call names, which it sends as the tag skills, and whether a prompt is empty, opens with a tag or marks an interruption, which it never keeps. It never keeps or sends a prompt or a reply, the model's thinking, a tool's input or output beyond that skill's name, a command, the path or contents of a file you work on, the working folder, a session's title or anyone's email, whatever the project's content mode.

Each plugin's failures are lines in a log of its own, naming the session, or a Codex subagent's thread, by the start of its id, the kind of thing that went wrong and, where Telmoni refused a turn, its answer, never anything said in the session. None of them holds the agent up: a plugin that runs as a hook hands its work to a process of its own and returns at once, in Cursor, which waits on every hook, as soon as Node has started.


Claude Code

Its repository is its marketplace, and names the plugin's release branch, which each release fills with the package as built, so the hook arrives as built and nothing else does. Installed so, it loads in every Claude Code you start, an editor's included:

claude plugin marketplace add telmoni/claude-observability-plugin
claude plugin install telmoni-observability@telmoni-plugins

From a checkout, start Claude Code with the plugin and your key in its environment instead; that one session alone loads it:

TELMONI_API_KEY=telmoni_… claude --plugin-dir ./claude-observability-plugin

In a Claude Code session, set the key with /plugin configure telmoni-observability@telmoni-plugins. From your shell, claude plugin configure telmoni-observability@telmoni-plugins --values-stdin takes the configuration as JSON ({"TELMONI_API_KEY": "telmoni_…"}); without --values-stdin it only lists the options. Claude Code keeps the key with its own credentials: in the Keychain on macOS, and in its credentials file elsewhere. Left blank, the endpoint is https://api.telmoni.com; a self-hosted Telmoni gives its own machine host. TELMONI_API_KEY and TELMONI_ENDPOINT in the environment serve when the plugin's own settings are unset.

Plugins load as a session starts, so start a new one once the key is set. The hook runs when a turn stops, when an API error ends one, and when the session ends; a turn that started a subagent in the background is sent once that subagent has finished too. Its log is ~/.claude/telmoni/hook.log (under CLAUDE_CONFIG_DIR where you set one). Inside Cursor, which runs Claude Code's hooks too, the plugin does nothing: Cursor's own plugin records those conversations.


Codex

You need Codex 0.143 or later. The plugin installs the same way, from its own repository, and Codex enables it in ~/.codex/config.toml:

codex plugin marketplace add telmoni/codex-observability-plugin
codex plugin add telmoni-observability@telmoni-plugins

Codex runs no plugin's hooks until you trust them: restart Codex and trust the plugin's hooks — at Stop, SubagentStop, Interrupt (from Codex 0.150) and SessionEnd (from 0.145) — when Codex asks, or in /hooks. codex exec asks nothing, so trust them once in Codex itself. Its repository's README says how to run it from a checkout.

The key goes in telmoni.json in Codex's folder — ~/.codex, or CODEX_HOME where you set one — as { "apiKey": "telmoni_…" }, with "endpoint" beside it for a self-hosted Telmoni; TELMONI_API_KEY and TELMONI_ENDPOINT in Codex's environment — your shell's, or the .env in Codex's folder — come first, and with both set the file is not read. The plugin reads the file only while it is yours and shut to everyone else (chmod 600): past one it passes over, it sends nothing, a key in the environment or not, since an endpoint the file names would otherwise give way to the default. Its log, which says why, is telmoni/hook.log in the same folder (~/.codex/telmoni/hook.log). A key in the file is read at every hook; one in Codex's environment, as a new session starts.

A turn is sent when Codex closes it, which may be minutes after you see its answer, while another plugin's hooks run or Codex compacts the session. Codex before 0.145 writes no error at a turn's end, so there a turn an error ended is recorded as one that completed. Codex's older session files hold a shell command's exit code only in its output, which the plugin never reads: there, a command that failed is recorded as one that ran.


OpenCode

You need OpenCode 1.18.29 or later, or OpenCode 2, whose plugin systems differ; the one package serves both. Add it to your opencode.json under plugin on OpenCode 1:

{ "plugin": ["@telmoni/opencode-observability-plugin"] }

or have OpenCode add it to the global configuration:

opencode plugin @telmoni/opencode-observability-plugin -g

On OpenCode 2, the list is plugins:

{ "plugins": ["@telmoni/opencode-observability-plugin"] }

or:

opencode plugin add @telmoni/opencode-observability-plugin

One list is enough: OpenCode 2 reads an OpenCode 1 plugin list too, and sets the plugin up once even where it is named in both. A checkout's build is named by its absolute path in place of the package: its dist/index.mjs on OpenCode 1, and its dist folder on OpenCode 2, which takes no file there.

The key goes in telmoni.json beside your OpenCode configuration (~/.config/opencode/telmoni.json, or in OPENCODE_CONFIG_DIR or under XDG_CONFIG_HOME where you set one), as { "apiKey": "telmoni_…" }, with "endpoint" beside it for a self-hosted Telmoni; TELMONI_API_KEY and TELMONI_ENDPOINT in the environment OpenCode starts in come first, and with both set the file is not read. The plugin never writes the file, and reads it only while it is yours and shut to everyone else (chmod 600): past one it passes over, it sends nothing, a key in the environment or not. Restart OpenCode once the key is in place: the plugin reads it as OpenCode loads it. The plugin runs inside OpenCode and keeps nothing on disk but its log, ~/.local/state/telmoni/opencode/plugin.log (under XDG_STATE_HOME where you set one). A turn Telmoni could not take — out of reach, busy, or its database failing it — is kept, the newest 200, and sent again every five minutes and as the next turn ends, while OpenCode runs (on OpenCode 1, the next in the same folder); one it took in part or refused for good, for its key too, is not, and the log says what it answered. As OpenCode shuts down, it waits three seconds at most for what is still to send, as it does when it reloads its plugins. A turn still running as OpenCode quits is not recorded, and one running as it reloads its plugins is recorded from the reload on.


Cursor

The package installs with npm and brings the command that wires it into Cursor; a checkout's build runs the same command:

npm install -g @telmoni/cursor-observability-plugin && telmoni-cursor-observability setup
node ./cursor-observability-plugin/dist/telmoni-cursor-observability.mjs setup   # from a checkout

setup asks for the key, or takes TELMONI_API_KEY, checks that it is shaped like one, and keeps it in ~/.cursor/telmoni.json, readable by you alone; then it adds the plugin's hooks to ~/.cursor/hooks.json, beside any other tool's. Cursor reloads that file as it changes, so your next prompt is recorded. The hooks read the key file only while it is yours and shut to everyone else, as setup leaves it: past one they pass over, they keep and send nothing, a key in their environment or not, and setup changes nothing until it is fixed or moved aside; with both TELMONI_API_KEY and TELMONI_ENDPOINT in their environment, they never read it. remove takes the hooks out again and deletes the key and ~/.cursor/telmoni/, but for what setup --project kept of a repository's hooks file, for that repository's remove --project. For a self-hosted Telmoni, set TELMONI_ENDPOINT to its machine host when you run setup, which keeps it beside the key; one the SDK would not send to, setup refuses with the SDK's reason, changing nothing. The hooks run the Node that ran setup, and the plugin it ran, by their paths, so a Cursor opened from the Dock finds them; keep a checkout where it is, and run setup again after moving to another Node. Its log is ~/.cursor/telmoni/hook.log.

Cursor's cloud agents read the repository's hooks alone: telmoni-cursor-observability setup --project writes .cursor/hooks.json there, to commit, with the package installed in the agents' environment (the install step of .cursor/environment.json), and the key as the Cloud Agents secret TELMONI_API_KEY. Those hooks run on your teammates' machines too, and send nothing there until a teammate runs setup, or has TELMONI_API_KEY in the environment Cursor runs its hooks with. On a machine where both are set up, the repository's hooks record its conversations and your own stand aside, unless the repository's cannot start, their command, telmoni-cursor-observability, being in no folder of Cursor's PATH, when your own record.

Cursor gives a turn's tokens, not each reply's, so a Cursor run holds one model call spanning the turn, its provider cursor. In Cursor's headless CLI (agent -p), which reports no prompt and no stop, a session's work is one run.