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.
| Agent | Plugin | Package | Runs as |
|---|---|---|---|
| Claude Code | telmoni/claude-observability-plugin | @telmoni/claude-observability-plugin | claude-code |
| Codex | telmoni/codex-observability-plugin | @telmoni/codex-observability-plugin | codex |
| OpenCode | telmoni/opencode-observability-plugin | @telmoni/opencode-observability-plugin | opencode |
| Cursor | telmoni/cursor-observability-plugin | @telmoni/cursor-observability-plugin | cursor |
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-pluginsFrom 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-pluginIn 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-pluginsCodex 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 -gOn OpenCode 2, the list is plugins:
{ "plugins": ["@telmoni/opencode-observability-plugin"] }or:
opencode plugin add @telmoni/opencode-observability-pluginOne 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 checkoutsetup 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.
Client SDKs
The Telmoni SDKs for Python, TypeScript / JavaScript, Go and Rust: an agent's runs, steps, model calls and tool calls as OpenTelemetry spans, sent to Telmoni and nowhere else.
Agent skill
The telmoni skill: what it teaches a coding agent about Telmoni, and how to install it in Claude Code, Codex, Cursor, Antigravity, OpenCode and other agents that read SKILL.md.