Oh My Pi Session Tracing with Future AGI
Export Oh My Pi (omp) coding-agent sessions, model calls, and tool calls to Future AGI with a project-local omp hook. Prompt and tool bodies stay off by default.
Copy one hook file into .omp/hooks/post/, set three environment variables, and every Oh My Pi session becomes one Future AGI trace: session, prompts, turns, model calls with token counts and cost, and tool calls. Prompts, answers, tool arguments, and tool results are not exported unless you set FI_CAPTURE_CONTENT=true.
Oh My Pi (omp) is a coding agent for the terminal, a fork of the Pi coding agent. This page adds a small omp hook that subscribes to omp’s documented hook events and sends them to Future AGI as OTLP traces. Each omp session shows up as one trace, with every model call and tool call inside it.
Use this guide with the omp.sh fork of Oh My Pi, not the upstream Pi binary.
| Install host | https://omp.sh/install |
| Binary | omp |
| npm package | @oh-my-pi/pi-coding-agent |
| Source | can1357/oh-my-pi (MIT) |
This is not the same as:
- Upstream Pi (
pi, pi.dev). Pi has its own extension API and its own page in this Coding agents section. Loading this file intopiis not supported: the hook only handles events whose context carries omp’s agent identity (ctx.agent), which Pi’s does not, so underpiit exports nothing. - omp’s built-in OpenTelemetry export (
OTEL_EXPORTER_OTLP_*). omp can send its own traces, logs, and metrics without any hook. This page uses a hook instead; see omp’s built-in OpenTelemetry export for the comparison and for how to avoid exporting twice. - Other forks or embedded SDKs. This hook targets only the omp.sh CLI hook API.
This recipe requires omp 18.11.0. The hook has no npm dependencies. See the hook source.
What you get
omp.session AGENT one per omp session
omp.prompt CHAIN one per prompt you submit
omp.turn CHAIN one per model/tool round trip
chat gpt-6-luna LLM the model call, with tokens and cost
execute_tool glob TOOL tools the model ran in that turn
execute_tool read TOOL
omp.turn CHAIN
chat gpt-6-luna LLM the call that wrote the answer
A session that switches to another session inside the same omp process ends its trace and starts a new one. That covers /new, /resume, and /fork (omp’s session_switch event), and a branch omp writes to a new session file: a promoted /btw answer, or a branch or entry fork requested by an extension or over RPC (omp’s session_branch event). /branch and /rewind in the TUI move within the same session file and keep the same session id, so the trace continues. A session you continue later with omp -c or omp -r gets a new trace with the same session.id, so all of its runs are grouped under Sessions. A session in which you never send a prompt (you open omp and quit, or leave it right away with /new) exports nothing.
Set up
Install omp from omp.sh
curl -fsSL https://omp.sh/install | sh -s -- --binary --ref v18.11.0
export PATH="$HOME/.local/bin:$PATH"
omp --versionThe flags above select the checksummed 18.11.0 prebuilt binary and install it to ~/.local/bin/omp; Bun is not required for this path. The PATH line makes that pinned binary available to your shell. Configure a model the way you normally do (/login, a provider key, or ~/.omp/agent/models.yml).
Add the hook to your project
From the root of the project you open with omp:
mkdir -p .omp/hooks/post
curl -fsSL https://raw.githubusercontent.com/future-agi/docs/main/examples/coding-agents/oh-my-pi/futureagi-otlp.ts \
-o .omp/hooks/post/futureagi-otlp.tsomp loads every .ts file in .omp/hooks/pre/ and .omp/hooks/post/ of the session’s working directory. post/ is omp’s convention for observers. The hook only applies to this project. To trace every project, put the file in ~/.omp/agent/hooks/post/ instead. Keep exactly one copy: a project copy and a user copy with the same file name in post/ load once, but a renamed copy, a second copy in pre/, or an extra copy loaded with --hook or extensions: sends every span twice. To try it for one run without installing it, use omp --hook ./futureagi-otlp.ts.
Set your Future AGI credentials
export FI_API_KEY="YOUR_API_KEY"
export FI_SECRET_KEY="YOUR_SECRET_KEY"
export FI_PROJECT_NAME="my-omp-project"If any of the three is missing, the hook does nothing.
Warning
If OTEL_EXPORTER_OTLP_ENDPOINT or OTEL_EXPORTER_OTLP_TRACES_ENDPOINT is set in this shell (for example for another coding agent), omp also sends its own spans there, and if that is Future AGI every model and tool call is stored twice. OTEL_EXPORTER_OTLP_ENDPOINT also makes omp send its own logs and metrics there. Turn off OTLP Telemetry Export in /settings, or start omp with OTEL_SDK_DISABLED=true omp. Either one turns off all of omp’s own export and leaves the hook working. See omp’s built-in OpenTelemetry export for what each switch turns off.
Run omp
omp
# or, one prompt without the TUI:
omp -p "List the files in this folder and read README.md"Both run the same agent session with hooks loaded. Run /extensions in the TUI to confirm that futureagi-otlp is loaded.
Open the trace
In Future AGI, open Observe, select my-omp-project, and open the newest omp.session trace. Spans for a turn are sent about a second after the turn ends, and the session span is updated with each batch and sent a last time when omp exits.
Configuration
| Variable | Default | Description |
|---|---|---|
FI_API_KEY | none (required) | Future AGI API key, sent as X-Api-Key. |
FI_SECRET_KEY | none (required) | Future AGI secret key, sent as X-Secret-Key. |
FI_PROJECT_NAME | none (required) | Project the traces are filed under (project_name resource attribute). |
FI_BASE_URL | https://api.futureagi.com | Collector base URL. /tracer/v1/traces is appended. Set this for a self-hosted Future AGI. |
FI_OTLP_ENDPOINT | $FI_BASE_URL/tracer/v1/traces | Full OTLP/HTTP traces URL. Overrides FI_BASE_URL. |
FI_CAPTURE_CONTENT | false | true exports prompt, answer, tool argument, tool result, tool intent, and error text. |
FI_OTLP_TIMEOUT_MS | 10000 | Timeout for each export request. |
FI_OTLP_DEBUG | false | true prints export results and failures to stderr. Failures are silent otherwise. The output can overwrite the TUI, so use it with omp -p. |
The hook posts OTLP/HTTP JSON with traces only. It never returns a value from a handler, so it never blocks or rewrites a tool call, a tool result, the context, or a message. It does not subscribe to tool_call, tool_result, context, input, or before_provider_request at all. It never runs shell commands and never reads omp’s session files. If an export fails, its spans are retried once with the next batch and then dropped. omp keeps working either way.
What is captured
| Span | omp events | Attributes |
|---|---|---|
omp.session | session_start, session_switch, session_branch, session_shutdown | session.id (from ctx.sessionManager.getSessionId()), gen_ai.agent.name=oh-my-pi, omp.mode (tui, print, json, rpc), omp.agent.kind, omp.agent.id, omp.agent.name, omp.agent.depth, omp.agent.parent_id, prompt, turn, model call, tool and tool error counts, summed tokens (omp.usage.input_tokens, omp.usage.output_tokens), summed cost (omp.usage.cost) |
omp.prompt | agent_start, message_end (user), agent_end | omp.prompt.index, omp.prompt.bytes, omp.stop_reason |
omp.turn | turn_start, turn_end | omp.turn.index, omp.turn.tool_count, omp.stop_reason |
chat <model> | message_end (assistant) | gen_ai.request.model, gen_ai.provider.name (the omp provider id), gen_ai.response.finish_reasons, gen_ai.usage.input_tokens (includes cache reads and writes), gen_ai.usage.output_tokens, gen_ai.usage.total_tokens, gen_ai.usage.cache_read.input_tokens, gen_ai.usage.cache_creation.input_tokens, omp.usage.reasoning_tokens, gen_ai.cost.input, gen_ai.cost.output, gen_ai.cost.total, omp.api, omp.llm.ttft_ms, omp.error.http_status, start and end times from omp |
execute_tool <tool> | tool_execution_start, tool_execution_end | gen_ai.tool.name, gen_ai.tool.call.id, omp.tool.input.bytes, omp.tool.output.bytes |
Every span carries session.id, so traces also appear under Sessions. A failed tool sets its span to ERROR with error.type=ToolError. A model call that ends with stop reason error sets the model, turn, and prompt spans to ERROR with error.type=ProviderError. By default all of these use the message error details omitted by default. With content capture on, the tool span’s message is the tool’s error output and the model span’s message is the provider’s error text; the turn and prompt spans keep the fixed message. The session span stays OK; its omp.tool.error.count shows how many tools failed.
What is not available
- The full model request. The hook does not read
before_provider_request, so the system prompt, history, and tool schemas are never exported. With content capture on, you get your prompt text and the assistant’s answer text, not the full request. - Thinking. Reasoning blocks are never exported, even with content capture on. Only the reasoning token count is.
- Streaming detail. Token deltas (
message_update) are not exported. Time to first token is exported asomp.llm.ttft_mswhen omp reports it. - Cancellation as an error. omp has no separate cancellation event. A model call you abort ends with stop reason
aborted, which is exported ingen_ai.response.finish_reasonswith anOKstatus. If omp shuts down in the middle of a turn, open spans are closed withomp.incomplete=trueand an unset status, notERROR. - Cost for models omp cannot price.
gen_ai.cost.*is omp’s own estimate from its model catalog or thecostyou set inmodels.yml. Calls omp reports as free get no cost attributes. - Subagents nested under the parent. omp gives each task-tool subagent its own copy of the hook, and each subagent session has its own session id, so every subagent becomes a separate trace carrying
omp.agent.kind,omp.agent.parent_id, andomp.agent.depth. It is not nested under the parent trace. - Hard kills. A turn’s spans are sent about a second after the turn ends. If omp is killed before that, that turn is lost. Earlier batches already carry a snapshot of the session and prompt spans, so the trace keeps its root.
Privacy
By default the hook exports ids, model and provider names, token counts, omp’s cost estimate, timings, tool names, status, and byte sizes. It does not export:
- your prompt text, the assistant’s answers, or thinking;
- tool arguments, tool results, or the tool
intenttext the model writes; - error messages (only the error type and, for model calls, the HTTP status);
- local paths: the session’s working directory (
omp.cwd) is not exported, because it usually contains your OS user name.
To export content, set FI_CAPTURE_CONTENT=true before starting omp. The prompt and final answer go to input.value and output.value on the prompt span (and the first prompt and last answer on the session span, which fills the trace list’s Input and Output columns), each model call’s text to its output.value, tool arguments and result text to the tool span, tool and provider error text to the status message of the tool or model span, and the working directory to omp.cwd on the session span. Each field is cut at 4,096 characters, and strings that look like sk-... keys, Bearer tokens, or api_key=... and "api_key": "..." values are replaced with [REDACTED].
Warning
Tool arguments and results can contain file contents, shell output, and secrets. Pattern redaction is not a data-loss-prevention control. Turn content capture on only for projects whose data you are allowed to store in Future AGI.
The hook runs inside omp with your user’s permissions and can see everything omp sees, including prompts and tool output. It is written to export only what the tables above list.
omp’s built-in OpenTelemetry export
omp also has its own OpenTelemetry export, separate from hooks and on by default. It covers three signals, sends OTLP/HTTP protobuf only, and starts each signal that has an endpoint (omp environment variables, OpenTelemetry export):
| Signal | Sent when this is set | What omp sends |
|---|---|---|
| Traces | OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, or OTEL_EXPORTER_OTLP_ENDPOINT (posts to <endpoint>/v1/traces) | Spans for each prompt, model call, and tool call (below) |
| Logs | OTEL_EXPORTER_OTLP_LOGS_ENDPOINT, or OTEL_EXPORTER_OTLP_ENDPOINT (/v1/logs) | omp’s own log lines, for example model discovery failed for provider with the provider’s URL, and a summary of each agent run |
| Metrics | OTEL_EXPORTER_OTLP_METRICS_ENDPOINT, or OTEL_EXPORTER_OTLP_ENDPOINT (/v1/metrics) | Token usage, estimated cost, and run, step, model-call, and tool-call counts |
So OTEL_EXPORTER_OTLP_ENDPOINT on its own turns on all three. The exporters also read OTEL_RESOURCE_ATTRIBUTES and the standard OTEL_EXPORTER_OTLP_HEADERS, which is how the Future AGI keys are passed. Future AGI accepts omp’s traces with no hook installed. Set only the traces endpoint, so omp’s logs and metrics stay off:
export OTEL_EXPORTER_OTLP_TRACES_ENDPOINT="https://api.futureagi.com/tracer/v1/traces"
export OTEL_EXPORTER_OTLP_HEADERS="X-Api-Key=YOUR_API_KEY,X-Secret-Key=YOUR_SECRET_KEY"
export OTEL_RESOURCE_ATTRIBUTES="project_name=my-omp-project,project_type=observe"
omp
The built-in export creates one trace per prompt:
invoke_agent no Future AGI span kind; gen_ai.conversation.id = omp session id
chat gpt-6-luna LLM model, provider, tokens
execute_tool glob TOOL
execute_tool read TOOL
chat gpt-6-luna LLM
| omp built-in export | This hook | |
|---|---|---|
| Setup | Three environment variables | One file per project (or per user) and three environment variables |
| Trace shape | One trace per prompt: invoke_agent with model and tool calls under it. No session or turn spans. | One trace per session: session, prompt, turn, model and tool calls |
| Sessions view | Not grouped. The omp session id is sent as gen_ai.conversation.id, not session.id. | Grouped by session.id |
| Exported by default | Model, provider, token counts, tool names and ids, tool descriptions, the model-written tool intent text (omp.gen_ai.tool.call.intent), and raw error messages, both in the span status and in an exception event with exception.message and exception.stacktrace (for example Path 'missing-notes.md' not found; when the model asks for an absolute path, the message contains it) | Ids, model, provider, tokens, cost, timings, tool names, byte sizes, fixed error types and messages. No intent or error text. |
| Logs and metrics | Also sent when their own endpoint or OTEL_EXPORTER_OTLP_ENDPOINT is set (see the table above) | None. Traces only. |
| Content opt-in | OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=summary or true (true includes the full request, system prompt and tool definitions) | FI_CAPTURE_CONTENT=true (prompt, answer, tool input and output, tool and provider error text; never the system prompt or thinking) |
| When spans leave | Batched: every 30 seconds and at process exit | About a second after each turn, and again at shutdown |
If you prefer the built-in export, do not install the hook. Do not use both.
Warning
While the hook is installed, keep omp’s own trace export away from Future AGI, or every model and tool call is stored twice in two differently shaped traces. Use one of the switches below. The first two also turn off omp’s logs and metrics.
Each switch turns off a different part of omp’s own export. The hook reads only the FI_* variables, so none of them affects it.
| Switch | What it turns off in omp |
|---|---|
OTLP Telemetry Export off under /settings (Providers tab, Privacy group), which is telemetry.otlpExportEnabled: false in ~/.omp/agent/config.yml | Traces, logs, and metrics, from the next launch |
OTEL_SDK_DISABLED=true omp | Traces, logs, and metrics |
OTEL_TRACES_EXPORTER=none omp | Traces only. This stops the duplicate traces, but if OTEL_EXPORTER_OTLP_ENDPOINT is set, omp still sends its logs and metrics there. Add OTEL_LOGS_EXPORTER=none OTEL_METRICS_EXPORTER=none to stop those as well. |
Unset OTEL_EXPORTER_OTLP_ENDPOINT and OTEL_EXPORTER_OTLP_TRACES_ENDPOINT before starting omp | Traces. Logs and metrics stop too, unless OTEL_EXPORTER_OTLP_LOGS_ENDPOINT or OTEL_EXPORTER_OTLP_METRICS_ENDPOINT is set; unset those as well to stop everything. |
A variable you set on the omp command line does not change your shell, but the commands omp runs for you inherit it. A program that uses OpenTelemetry and that you run through omp’s bash tool would see OTEL_SDK_DISABLED=true too. The /settings switch does not change the environment.
Troubleshooting
No traces appear
Check that FI_API_KEY, FI_SECRET_KEY, and FI_PROJECT_NAME are set in the shell that starts omp, and start omp from the project root so it finds .omp/hooks/post/futureagi-otlp.ts. Run /extensions in the TUI to see whether the hook loaded. Set FI_OTLP_DEBUG=true to print lines such as exported 6 spans in 40 ms or collector returned HTTP 401 to stderr. For a self-hosted Future AGI, set FI_BASE_URL or FI_OTLP_ENDPOINT.
omp reports an invalid extension at startup
omp prints Failed to load extension <path>: <error> at startup, in the TUI and with omp -p; the error names the file and the build or import problem. The file must stay a direct child of hooks/pre or hooks/post and keep its default export. In the TUI, /extensions shows which hook files were loaded and from where. Do not copy the file into a Pi (pi) extensions folder: it targets omp’s hook API.
The last turn of a session is missing
omp gives session_shutdown handlers 2 seconds. The hook sends everything left in one request at shutdown and waits up to 1.8 seconds for the collector. A collector that answers slower than that (for example on its first request after a cold start) can lose the final batch, which leaves the session span with the end time of the previous batch. Check the collector’s latency with FI_OTLP_DEBUG=true.
Every span appears twice
Two copies of the hook are loaded. omp loads a file name in post/ once even if it is in both ~/.omp/agent/hooks/post/ and the project’s .omp/hooks/post/, so duplicates come from a renamed second copy, a copy in pre/ as well as post/, or a copy also passed with --hook or listed under extensions: in config.yml. Run /extensions in the TUI and keep one copy.
The same calls also appear as invoke_agent traces
omp’s own OpenTelemetry export is also sending to Future AGI, because OTEL_EXPORTER_OTLP_ENDPOINT or OTEL_EXPORTER_OTLP_TRACES_ENDPOINT is set in the shell that starts omp. Its traces have an invoke_agent root instead of omp.session. Turn off OTLP Telemetry Export in /settings, or start omp with OTEL_SDK_DISABLED=true; both also stop omp’s own logs and metrics. OTEL_TRACES_EXPORTER=none stops these traces only. See omp’s built-in OpenTelemetry export for what each switch turns off.
Token counts look higher than the provider's input count
gen_ai.usage.input_tokens includes cached input (cache reads plus cache writes), following the OpenTelemetry GenAI conventions. omp’s own usage.input excludes cached tokens. The cache counts are also exported separately.
A binary called omp from another fork
Run omp --version and check that omp came from https://omp.sh/install or the @oh-my-pi/pi-coding-agent package. Other forks are not supported by this page.
Related guides
Questions & Discussion