Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions packages/sample-app/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
# sample-app

Project description here.

## Governed agent action metadata

`sample_app/governed_action_span_attributes.py` shows how to attach
vendor-neutral governance metadata to an OpenTelemetry span for an agent action.
The example includes an action reference and hash, governance verdict, approval
status, proof URL, and external verifier reference as custom span attributes.
45 changes: 45 additions & 0 deletions packages/sample-app/sample_app/governed_action_span_attributes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
"""
Example: attach governed agent action metadata to OpenTelemetry spans.

These attributes are illustrative custom span attributes. They can help connect
an agent's proposed action to the governance decision, approval state, proof
record, and an external verifier reference without requiring a vendor-specific
collector or backend.
"""

from opentelemetry import trace
from traceloop.sdk import Traceloop


ACTION_ATTRIBUTES = {
"gen_ai.agent.action.ref": "tool:crm.update_customer",
"gen_ai.agent.action.hash": (
"sha256:6f1f2d8a3c8c9e7a0b4d5e6f7890abcd1234567890abcdef1234567890abcdef"
),
"governance.verdict": "allow",
"governance.approval.status": "approved",
"governance.proof.url": "https://governance.example/proofs/run-9b7c/action-42",
"governance.external_verifier.ref": "verifier://policy-engine/prod/decision-42",
}


def main():
Traceloop.init(
app_name="governed-action-span-attributes",
disable_batch=True,
instruments={},
Comment on lines +27 to +30

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

rg -n 'traceloop-sdk|Traceloop.init|instruments' . \
  --glob 'pyproject.toml' \
  --glob 'uv.lock' \
  --glob '*.py'

uv run --with traceloop-sdk python - <<'PY'
from inspect import getsource, signature
from traceloop.sdk import Traceloop
from traceloop.sdk.tracing import init_instrumentations

print(signature(Traceloop.init))
print(getsource(init_instrumentations))
PY

Repository: traceloop/openllmetry

Length of output: 50379


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

echo "## relevant source files"
wc -l packages/traceloop-sdk/traceloop/sdk/__init__.py packages/traceloop-sdk/traceloop/sdk/instruments.py packages/traceloop-sdk/traceloop/sdk/tracing.py 2>/dev/null || true

echo "## packages/traceloop-sdk/traceloop/sdk/__init__.py lines 1-220"
sed -n '1,220p' packages/traceloop-sdk/traceloop/sdk/__init__.py

echo "## packages/traceloop-sdk/traceloop/sdk/tracing.py lines 1-140"
sed -n '1,140p' packages/traceloop-sdk/traceloop/sdk/tracing.py

echo "## packages/traceloop-sdk/traceloop/sdk/instruments.py"
sed -n '1,80p' packages/traceloop-sdk/traceloop/sdk/instruments.py

echo "## targeted samples with empty dict"
rg -n "Traceloop\.init\([^)]*instruments=\{\}" packages/sample-app/sample_app -g '*.py'

Repository: traceloop/openllmetry

Length of output: 9793


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

echo "## tracing package files"
git ls-files packages/traceloop-sdk/traceloop/sdk | sed -n '1,120p'

echo "## init_instrumentations references"
rg -n "init_instrumentations|TracerWrapper|def __init__|instruments|block_instruments" packages/traceloop-sdk/traceloop/sdk -g '*.py'

echo "## candidate files with init_instrumentations"
rg -l "init_instrumentations" packages/traceloop-sdk/traceloop/sdk -g '*.py'

Repository: traceloop/openllmetry

Length of output: 12747


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

echo "## packages/traceloop-sdk/traceloop/sdk/tracing/tracing.py lines 140-210"
sed -n '140,210p' packages/traceloop-sdk/traceloop/sdk/tracing/tracing.py

echo "## packages/traceloop-sdk/traceloop/sdk/tracing/tracing.py lines 480-540"
sed -n '480,535p' packages/traceloop-sdk/traceloop/sdk/tracing/tracing.py

echo "## packages/sample-app/sample_app/governed_action_span_attributes.py lines 1-60"
sed -n '1,60p' packages/sample-app/sample_app/governed_action_span_attributes.py

echo "## other empty-instruments samples"
rg -n "Traceloop\.init\([^)]*instruments=\{\}" packages/sample-app/sample_app -g '*.py'

echo "## init_instrumentations import target"
grep -n "from .*init_instrumentations" packages/traceloop-sdk/traceloop/sdk/tracing/tracing.py
sed -n '1,45p' packages/traceloop-sdk/traceloop/sdk/tracing/tracing.py

Repository: traceloop/openllmetry

Length of output: 7234


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

echo "## behavioral probe for empty instruments handling"
python3 - <<'PY'
class Instrument:
    pass

Instrument.A = object()

instruments_dict = {}
block_instruments_dict = {}

block_instruments_dict = block_instruments_dict or set()
instruments_after_none = instruments_dict if instruments_dict is not None else {Instrument.A}
try:
    instruments_subtracted = instruments_after_none - block_instruments_dict
    print("dict_subtraction_result_repr=" + repr(instruments_subtracted))
except Exception as e:
    print("dict_subtraction_error_type=" + type(e).__name__)
    print("dict_subtraction_error_message=" + str(e))

instruments_set = set()
instruments_subtracted_set = (instruments_set if instruments_set is not None else {Instrument.A}) - (block_instruments_dict or set())
print("set_subtraction_result_repr=" + repr(instruments_subtracted_set))
PY

echo "## uv package availability"
if command -v uv >/dev/null 2>&1; then
  echo "uv present"
  uv run python3 - <<'PY'
try:
    from traceloop.sdk.tracing.tracing import init_instrumentations
    from traceloop.sdk.instruments import Instruments
    import inspect
    print(init_instrumentations.__module__)
    print(inspect.signature(init_instrumentations).parameters["instruments"].annotation)
    init_instrumentations(False, lambda *a, **k: None, instruments={}, block_instruments={})
    print("empty_dict_call=ok")
except Exception as e:
    print(type(e).__name__ + ": " + str(e))
PY
else
  echo "uv not present"
fi

echo "## local tests mentioning empty instruments"
rg -n "instruments=\{\}|instruments=set\(\)|block_instruments" packages/traceloop-sdk/tests packages/sample-app/sample_app -g '*.py'

Repository: traceloop/openllmetry

Length of output: 723


Use no instruments arg to disable instrumentation.

Traceloop.init(instruments={}) passes a dict to init_instrumentations(), which later does instruments = instruments - block_instruments. That raises TypeError: unsupported operand type(s) for -: 'dict' and 'set'. Use the default value instead.

Proposed fix
-    Traceloop.init(
-        app_name="governed-action-span-attributes",
-        disable_batch=True,
-        instruments={},
-    )
+    Traceloop.init(
+        app_name="governed-action-span-attributes",
+        disable_batch=True,
+    )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/sample-app/sample_app/governed_action_span_attributes.py` around
lines 27 - 30, Remove the empty instruments argument from the Traceloop.init
call in the governed action span attributes setup, allowing
init_instrumentations() to use its default value and avoid the dict/set
subtraction error.

)
Comment on lines +27 to +31

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== files =="
git ls-files | rg '(^|/)governed_action_span_attributes\.py$|pyproject\.toml$|Traceloop' || true

echo
echo "== target file =="
if [ -f packages/sample-app/sample_app/governed_action_span_attributes.py ]; then
  nl -ba packages/sample-app/sample-app/governed_action_span_attributes.py 2>/dev/null || nl -ba packages/sample-app/sample_app/governed_action_span_attributes.py
fi

echo
echo "== dependency/version clues =="
rg -n "traceloop|opentelemetry|ConsoleSpanExporter|API_KEY|TRACLOOP" . -g '!**/.git/**' -g '!**/node_modules/**' | head -200

echo
echo "== package metadata =="
fd -a 'pyproject\.toml$' . | while read -r f; do
  echo "--- ${f#"$PWD/"}"
  sed -n '1,220p' "$f" | rg -n "traceloop|opentelemetry|packages|dependencies|requires-python|python" -C 2 || true
done

Repository: traceloop/openllmetry

Length of output: 2443


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== target file =="
sed -n '1,160p' packages/sample-app/sample_app/governed_action_span_attributes.py | awk '{printf "%6d  %s\n", NR, $0}'

echo
echo "== sample pyproject =="
sed -n '1,220p' packages/sample-app/pyproject.toml | awk '{printf "%6d  %s\n", NR, $0}'

echo
echo "== traceloop sdk relevant init/export docs in repo =="
python3 - <<'PY'
import pathlib, re
for p in pathlib.Path("packages").rglob("*.py"):
    try:
        text = p.read_text(encoding="utf-8", errors="ignore")
    except Exception:
        continue
    if "def init" in text and ("Traceloop.init" in text or "class Traceloop" in text):
        print("----", p)
        lines = text.splitlines()
        for i, line in enumerate(lines, start=1):
            if "class Traceloop" in line or "def init" in line:
                a=max(1,i-12); b=min(len(lines),i+120)
                for j in range(a,b+1):
                    print(f"{j:6d}  {lines[j-1]}")
PY

echo
echo "== search ConsoleSpanExporter and API key env usage =="
rg -n "ConsoleSpanExporter|Traceloop\.init|api_key|API_KEY|TRACLOOP|disable_batch" packages -g '*.py' -g '*.toml' | head -300

echo
echo "== deterministic parse probe for target snippet =="
python3 - <<'PY'
import ast, pathlib
p=pathlib.Path("packages/sample-app/sample_app/governed_action_span_attributes.py")
text=p.read_text()
tree=ast.parse(text)
for node in ast.walk(tree):
    if isinstance(node, ast.Call):
        if isinstance(node.func, ast.Attribute) and node.func.attr == "init":
            print("Traceloop.init args:", {kw.arg: ast.unparse(kw.value) for kw in node.keywords})
PY

Repository: traceloop/openllmetry

Length of output: 50378


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== target file relevant lines =="
awk '{printf "%6d  %s\n", NR, $0}' packages/sample-app/sample_app/governed_action_span_attributes.py | sed -n '1,120p'
awk '{printf "%6d  %s\n", NR, $0}' packages/sample-app/sample_app/governed_action_span_attributes.py | sed -n '120,200p'

echo
echo "== console/exporter imports in target and nearby sample files =="
rg -n "import tr\." packages/sample-app/sample_app packages/sample-app -g '*.py' | head -100
rg -n "ConsoleSpanExporter|import exporter|exporter=" packages/sample-app/sample_app packages/sample-app -g '*.py' | head -150

echo
echo "== parse Traceloop.init call in target =="
python3 - <<'PY'
import ast, pathlib
p=pathlib.Path("packages/sample-app/sample_app/governed_action_span_attributes.py")
text=p.read_text()
tree=ast.parse(text)
for node in ast.walk(tree):
    if isinstance(node, ast.Call):
        func=node.func
        if isinstance(func, ast.Attribute) and func.attr == "init":
            if isinstance(func.value, ast.Name) and func.value.id == "Traceloop":
                print("Traceloop.init call at line", node.lineno)
                print("keywords:", {kw.arg or "<positional>": ast.unparse(kw.value) for kw in node.keywords})
                print("has exporter keyword:", any(kw.arg == "exporter" for kw in node.keywords))
PY

echo
echo "== focused search SDK init and API key handling =="
rg -n "def init\(|API_KEY|TRACLOOP_API|tracer_provider|ConsoleSpanExporter|disable_batch|get_tracer_provider|set_tracer_provider" packages -g '*.py' | head -250

echo
echo "== focused python files containing relevant SDK init implementation =="
rg -l "class Traceloop|def init\(" packages -g '*.py' | head -20
for f in $(rg -l "class Traceloop|def init\(" packages -g '*.py' | head -10); do
  echo "--- $f"
  rg -n "class Traceloop|def init\(|API_KEY|TRACLOOP_API|tracer_provider|ConsoleSpanExporter|disable_batch|get_tracer_provider|set_tracer_provider" "$f" -C 8
done

Repository: traceloop/openllmetry

Length of output: 2120


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== focused list files =="
rg -l "class Traceloop|def init\(" packages -g '*.py' | python3 - <<'PY'
import sys
lines=sys.stdin.read().strip().splitlines()
for line in lines:
    if line:
        print(line)
PY

echo
echo "== focused implementation excerpts =="
while IFS= read -r f; do
  echo "--- $f"
  rg -n "class Traceloop|def init\(|API_KEY|TRACLOOP_API|tracer_provider|ConsoleSpanExporter|disable_batch|get_tracer_provider|set_tracer_provider|_init_.*API|exporter" "$f" -C 6 || true
done

echo
echo "== target file parser probe =="
python3 - <<'PY'
import ast, pathlib
p=pathlib.Path("packages/sample-app/sample_app/governed_action_span_attributes.py")
text=p.read_text()
tree=ast.parse(text)
for node in ast.walk(tree):
    if isinstance(node, ast.Call):
        func=node.func
        if isinstance(func, ast.Attribute) and func.attr == "init":
            if isinstance(func.value, ast.Name) and func.value.id == "Traceloop":
                print("Traceloop.init line", node.lineno)
                print("keywords:", [(kw.arg or "<positional>", ast.unparse(kw.value)) for kw in node.keywords])
                print("has exporter keyword:", any(kw.arg == "exporter" for kw in node.keywords))
PY

echo
echo "== deterministic env/default init-flow probe from source text =="
python3 - <<'PY'
import re, pathlib, ast
files=[pathlib.Path(p) for p in open("/tmp/focused_files.txt")]
# Fallback list if no temp file, but avoid shell subshell failure above.
PY

Repository: traceloop/openllmetry

Length of output: 5011


🏁 Script executed:

#!/bin/bash
set -euo pipefail

tmp="$(mktemp)"
trap 'rm -f "$tmp"' EXIT

echo "== focused files list =="
rg -l "class Traceloop|def init\(" packages -g '*.py' > "$tmp" || true
cat "$tmp"

echo
echo "== focused implementation excerpts =="
while IFS= read -r f; do
  echo "--- $f"
  rg -n "class Traceloop|def init\(|API_KEY|TRACLOOP_API|tracer_provider|ConsoleSpanExporter|disable_batch|get_tracer_provider|set_tracer_provider|exporter" "$f" -C 8 || true
done < "$tmp"

echo
echo "== target file parser probe =="
python3 - <<'PY'
import ast, pathlib
p=pathlib.Path("packages/sample-app/sample_app/governed_action_span_attributes.py")
text=p.read_text()
tree=ast.parse(text)
for node in ast.walk(tree):
    if isinstance(node, ast.Call):
        func=node.func
        if isinstance(func, ast.Attribute) and func.attr == "init":
            if isinstance(func.value, ast.Name) and func.value.id == "Traceloop":
                print("Traceloop.init line", node.lineno)
                print("keywords:", [(kw.arg or "<positional>", ast.unparse(kw.value)) for kw in node.keywords])
                print("has exporter keyword:", any(kw.arg == "exporter" for kw in node.keywords))
PY

Repository: traceloop/openllmetry

Length of output: 11822


Use ConsoleSpanExporter for this standalone sample.

Traceloop.init has no local exporter, and this sample does not set TRACELOOP_API_KEY. That makes the SDK print the default missing-API-key error and return before installing the tracer wrapper, so the local tracer.start_as_current_span(...) call and success message only run when credentials are provided externally.

Proposed fix
 from opentelemetry import trace
+from opentelemetry.sdk.trace.export import ConsoleSpanExporter
 ...
     Traceloop.init(
         app_name="governed-action-span-attributes",
         disable_batch=True,
+        exporter=ConsoleSpanExporter(),
         instruments={},
     )

As required by the Python guidelines, use ConsoleSpanExporter from opentelemetry.sdk.trace.export for debugging OpenTelemetry spans and hierarchy issues.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Traceloop.init(
app_name="governed-action-span-attributes",
disable_batch=True,
instruments={},
)
Traceloop.init(
app_name="governed-action-span-attributes",
disable_batch=True,
exporter=ConsoleSpanExporter(),
instruments={},
)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/sample-app/sample_app/governed_action_span_attributes.py` around
lines 27 - 31, Update the Traceloop.init call in the standalone sample to
configure a ConsoleSpanExporter imported from opentelemetry.sdk.trace.export,
ensuring spans are emitted locally without requiring TRACELOOP_API_KEY and the
existing tracer flow remains executable.

Source: Coding guidelines


tracer = trace.get_tracer("sample_app.governed_action_span_attributes")

with tracer.start_as_current_span("agent.action") as span:
for key, value in ACTION_ATTRIBUTES.items():
span.set_attribute(key, value)

# Run the approved action here. OSuite or another governance emitter can
# provide these values before the action is executed.
print("Recorded governed action metadata on the current span.")


if __name__ == "__main__":
main()