Record keys and versions
Two things make a run easy to follow up on later: which business record it acted on (a support ticket, an order, a pull request) and which version of your agent ran. Record keys carry the first. Version fields carry the second. Both are optional, additive, and cost one line each.
Requires SDK 0.7.0 (not yet released; 0.6.0 does not have this). On 0.6.0, run() fails
with run() got an unexpected keyword argument 'agent_version'.
SDK support for everything on this page ships in SDK 0.7.0, which is not released yet. You can send the same data today over REST and OTLP; each section below shows the wire form next to the SDK call.
Record keys
A record key names the business record a run acted on. It is a pair (key_type, key_value),
for example ("zendesk_ticket", "4812"). A run can carry several.
Once a run carries a key, you can send the outcome of that record later (a ticket was reopened, an order was refunded, a pull request was merged) without knowing the run’s trace id. Morse finds every run that carries the key and labels it.
For pull requests connected through the GitHub connector, use the key type
github_pr with the value owner/repo#number; a bare number never matches.
Rules, enforced when the run arrives:
key_typeis lower-cased and must match^[a-z0-9][a-z0-9_.-]{0,63}$.key_valueis turned into a string and trimmed, and must be 1–256 characters. Integers are accepted, so an unquoted ticket id from a webhook template works.- At most 20 keys are kept per run.
- An invalid pair is dropped. The run is still accepted, so a bad key never costs you a trace.
Key values are opaque identifiers. Use a ticket number, order id or PR number, not an email address, name or anything else that identifies a person. Keys are stored and searchable as plain text.
Python
import morse_ai
morse_ai.init()
with morse_ai.run("support-agent", record_keys={"zendesk_ticket": ticket.id}) as r:
answer = handle(ticket)
replied = bool(answer) # derive the outcome from a check; never hard-code success=True
r.set_outcome(success=replied, outcome="replied" if replied else "no_reply")You often learn the id after the run has started. Add it from anywhere inside the run:
morse_ai.link_record("zendesk_ticket", ticket.id)If you already use set_context(), any key that starts with record_key. is
treated as a record key. It also stays in your user_metadata, so your Segments keep working:
morse_ai.set_context(**{"record_key.zendesk_ticket": ticket.id})TypeScript
import * as morse from "@morsehq-dev/sdk";
await morse.runTraced(
{ agentName: "support-agent", recordKeys: { zendesk_ticket: ticket.id } },
async () => {
// ...
morse.linkRecord("order", order.id); // add another key mid-run
morse.setContext({ "record_key.github_pr": pr.number }); // same prefix convention as Python
},
);REST and OTLP
Available today, without the SDK.
| Route | How to send a record key |
|---|---|
| REST trace payload | "record_keys": {"zendesk_ticket": "4812"} on the trace |
REST user_metadata | "user_metadata": {"record_key.zendesk_ticket": "4812"} |
| OTLP span | span attribute morse.record_key.zendesk_ticket on any span of the run |
OTLP record-key attributes are consumed by Morse and are not copied into the span’s metadata. All three routes can be combined on one run; the keys are merged.
Agent version
agent_version is your own label for the deployed agent: a release name, a build number, a
semantic version. Up to 128 characters.
morse_ai.init(agent_version="2026.10.3") # default for every run
with morse_ai.run("support-agent", agent_version="2026.10.3-canary") as r: # this run only
...morse.init({ agentVersion: "2026.10.3" });
await morse.runTraced(
{ agentName: "support-agent", agentVersion: "2026.10.3-canary" }, // this run only
async () => {},
);Over the wire: "agent_version" on the REST trace payload, or the span attribute
morse.agent_version on any span over OTLP (the first value wins; the SDKs send it on the root
span).
Prompt references
Say which prompt a run used. The version is optional, so teams that keep prompts in code do not
have to invent version numbers. Your agent_version or git revision identifies the change instead.
morse_ai.set_prompt("triage", version=7)
morse_ai.set_prompt("triage") # prompts kept in code: no version neededmorse.setPrompt("triage", 7);
morse.setPrompt("triage");Called inside an open span, the reference is pinned to that span; otherwise it belongs to the run. At most 20 references are kept per run. A version that is not a whole number of 1 or more is dropped and the name is kept.
Over the wire: "prompts": [{"name": "triage", "version": 7}] on the REST trace payload, or the
span attributes morse.prompt.name and morse.prompt.version over OTLP (one reference per span).
A reference that includes a version is linked to the matching prompt template. One without a
version is recorded on the run and shows up as triage in its configuration.
Git revision
The SDKs look for the revision that is running, in this order, and use the first one that is set:
MORSE_GIT_REVISIONGITHUB_SHACI_COMMIT_SHARAILWAY_GIT_COMMIT_SHAVERCEL_GIT_COMMIT_SHARENDER_GIT_COMMITSOURCE_VERSIONgit rev-parse HEAD, when a git binary and checkout are present. In TypeScript this only runs where Node’schild_processis available. It never throws.
It is sent as the git.revision resource attribute, over REST and OTLP alike. Morse trims it and
keeps the first 40 characters.
In a container
A production image usually has no .git directory and no CI variables, so none of the lookups
above find anything. Pass the commit in at build time:
ARG GIT_SHA
ENV MORSE_GIT_REVISION=$GIT_SHAdocker build --build-arg GIT_SHA="$(git rev-parse HEAD)" -t my-agent .If your platform already sets one of the variables in the list at runtime (Railway, Vercel and Render do), you do not need this.
Service version
If your process sets service.version in OTEL_RESOURCE_ATTRIBUTES, both SDKs send it with no
extra code, and OTLP exporters send it as a standard resource attribute. Morse keeps up to 128
characters.
export OTEL_RESOURCE_ATTRIBUTES="service.name=support-agent,service.version=2.4.1"Which version field do I set?
| Field | Answers | Set it with | When to use it |
|---|---|---|---|
| Git revision | Which commit is running? | Nothing, usually. MORSE_GIT_REVISION in a container. | Always. It is free and exact. |
agent_version | Which release of the agent did you declare? | init(agent_version=) or run(agent_version=) | You name releases, canaries or experiments your own way. |
| Prompt version | Which prompt text did this run use? | set_prompt(name, version) | You keep prompts in a registry with numbered versions. |
service.version | Which version of the service is this process? | OTEL_RESOURCE_ATTRIBUTES | Your service already reports one through OpenTelemetry. |
You do not need all four. If you set only one, set the git revision: it needs no code and cannot drift from what is deployed.
Where it shows up
Attribution panel
Open any run from Traces. The Attribution panel lists the run’s record keys, its agent version, git revision and service version, the models, prompt references and tool descriptions it used, and the history of its outcome label: when it changed, what it changed to, and where the label came from. A field that was never sent reads Not captured and links back to this page.
Capture health
Open an agent. The Capture health section answers whether this is wired up across all of the agent’s runs, not just the one you tested. It shows, for the selected window:
- the share of runs with any record key, and the count per key type;
- the share of keyed runs that received a record outcome;
- the share of runs with a git revision, and with a configuration fingerprint;
- record-outcome events by status: applied, pending, expired, no effect.
Each percentage comes with its counts, for example “412 of 1,030 runs”. A low keyed share usually
means one code path forgot to call link_record. A growing pending count usually means events are
arriving with a key_type or value that no run carries.
Related
- Record outcomes by key: send what happened to a record, and Morse labels every run that carries its key.
- set_context(): the
record_key.prefix convention builds on it. - OTLP: the span attributes above, for OpenTelemetry exporters.