rootmark 0.1.0__tar.gz → 0.1.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rootmark-0.1.2/CHANGELOG.md +144 -0
- {rootmark-0.1.0 → rootmark-0.1.2}/PKG-INFO +72 -1
- rootmark-0.1.2/README.md +167 -0
- {rootmark-0.1.0 → rootmark-0.1.2}/pyproject.toml +4 -2
- {rootmark-0.1.0 → rootmark-0.1.2}/src/rootmark/__init__.py +300 -234
- {rootmark-0.1.0 → rootmark-0.1.2}/src/rootmark/config.py +267 -267
- {rootmark-0.1.0 → rootmark-0.1.2}/src/rootmark/core/emitter.py +219 -8
- rootmark-0.1.2/src/rootmark/core/lifecycle.py +94 -0
- rootmark-0.1.2/src/rootmark/core/step.py +72 -0
- {rootmark-0.1.0 → rootmark-0.1.2}/src/rootmark/patches/_bedrock_helpers.py +207 -169
- {rootmark-0.1.0 → rootmark-0.1.2}/src/rootmark/patches/boto3_interceptor.py +28 -4
- {rootmark-0.1.0 → rootmark-0.1.2}/src/rootmark/patches/crewai.py +480 -480
- {rootmark-0.1.0 → rootmark-0.1.2}/src/rootmark/patches/strands.py +685 -669
- {rootmark-0.1.0 → rootmark-0.1.2}/src/rootmark/patches/strands_multiagent.py +576 -571
- rootmark-0.1.2/src/rootmark/schema/trace_event.py +129 -0
- {rootmark-0.1.0 → rootmark-0.1.2}/tests/core/test_emitter_bind_context.py +130 -129
- {rootmark-0.1.0 → rootmark-0.1.2}/tests/core/test_emitter_session.py +106 -105
- {rootmark-0.1.0 → rootmark-0.1.2}/tests/patches/test_agentcore.py +545 -544
- {rootmark-0.1.0 → rootmark-0.1.2}/tests/patches/test_strands.py +300 -299
- {rootmark-0.1.0 → rootmark-0.1.2}/tests/test_boto3_interceptor.py +1282 -1216
- {rootmark-0.1.0 → rootmark-0.1.2}/tests/test_config_override.py +229 -221
- {rootmark-0.1.0 → rootmark-0.1.2}/tests/test_crewai_patch.py +182 -180
- {rootmark-0.1.0 → rootmark-0.1.2}/tests/test_langchain_handler.py +99 -98
- rootmark-0.1.2/tests/test_span_lifecycle.py +356 -0
- {rootmark-0.1.0 → rootmark-0.1.2}/tests/test_span_tags.py +218 -201
- rootmark-0.1.2/tests/test_step_contract.py +53 -0
- {rootmark-0.1.0 → rootmark-0.1.2}/tests/test_strands_agent_live.py +320 -303
- {rootmark-0.1.0 → rootmark-0.1.2}/tests/test_strands_multiagent.py +472 -454
- {rootmark-0.1.0 → rootmark-0.1.2}/tests/test_strands_multiagent_live.py +404 -387
- {rootmark-0.1.0 → rootmark-0.1.2}/tests/test_strands_patch.py +676 -675
- {rootmark-0.1.0 → rootmark-0.1.2}/tests/test_strands_session.py +307 -290
- {rootmark-0.1.0 → rootmark-0.1.2}/tests/test_suppress_payloads.py +202 -185
- {rootmark-0.1.0 → rootmark-0.1.2}/tests/test_trace_and_span.py +384 -383
- rootmark-0.1.0/README.md +0 -96
- rootmark-0.1.0/src/rootmark/schema/trace_event.py +0 -83
- {rootmark-0.1.0 → rootmark-0.1.2}/.gitignore +0 -0
- {rootmark-0.1.0 → rootmark-0.1.2}/src/rootmark/audit/__init__.py +0 -0
- {rootmark-0.1.0 → rootmark-0.1.2}/src/rootmark/audit/hashing.py +0 -0
- {rootmark-0.1.0 → rootmark-0.1.2}/src/rootmark/core/__init__.py +0 -0
- {rootmark-0.1.0 → rootmark-0.1.2}/src/rootmark/core/processor.py +0 -0
- {rootmark-0.1.0 → rootmark-0.1.2}/src/rootmark/patches/__init__.py +0 -0
- {rootmark-0.1.0 → rootmark-0.1.2}/src/rootmark/patches/agentcore.py +0 -0
- {rootmark-0.1.0 → rootmark-0.1.2}/src/rootmark/patches/langchain.py +0 -0
- {rootmark-0.1.0 → rootmark-0.1.2}/src/rootmark/patches/openai.py +0 -0
- {rootmark-0.1.0 → rootmark-0.1.2}/src/rootmark/patches/strands_session.py +0 -0
- {rootmark-0.1.0 → rootmark-0.1.2}/src/rootmark/schema/__init__.py +0 -0
- {rootmark-0.1.0 → rootmark-0.1.2}/tests/core/test_processor_envelope.py +0 -0
- {rootmark-0.1.0 → rootmark-0.1.2}/tests/integration/conftest.py +0 -0
- {rootmark-0.1.0 → rootmark-0.1.2}/tests/integration/test_strands_e2e.py +0 -0
- {rootmark-0.1.0 → rootmark-0.1.2}/tests/patches/__init__.py +0 -0
- {rootmark-0.1.0 → rootmark-0.1.2}/tests/test_roundtrip_ingestion.py +0 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
Notable changes to the `rootmark` SDK. Versions follow the collector's own
|
|
4
|
+
releases loosely; where the two must match, it says so.
|
|
5
|
+
|
|
6
|
+
## 0.1.2
|
|
7
|
+
|
|
8
|
+
**Fixes a defect in 0.1.1 that stopped runs ever completing.** Upgrade from
|
|
9
|
+
0.1.1; there is no reason to stay on it.
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- **A discarded span held its run open for ever.** 0.1.1 made `start_span`
|
|
14
|
+
write a provisional row, but `discard_span` still emitted nothing -- correct
|
|
15
|
+
when a discarded span had never been mentioned, wrong once it had. The
|
|
16
|
+
provisional row said `running` and nothing superseded it, and a run is
|
|
17
|
+
finished only when all of its spans are. Every run containing a discarded
|
|
18
|
+
span therefore stayed in progress permanently.
|
|
19
|
+
|
|
20
|
+
It was not a corner case. The boto3 interceptor discards its span on *every*
|
|
21
|
+
Bedrock call made inside a Strands agent, because the Strands patch is
|
|
22
|
+
already tracing the same call. On the first deployment running both, 18 of
|
|
23
|
+
18 runs recorded after the upgrade were stuck "running" and none could ever
|
|
24
|
+
complete.
|
|
25
|
+
|
|
26
|
+
Two changes, because one of them is a guarantee and the other is the right
|
|
27
|
+
shape:
|
|
28
|
+
|
|
29
|
+
* `discard_span` now **retracts** a span it announced, writing a terminal
|
|
30
|
+
`cancelled` row at the end sentinel. Any caller that announced a span can
|
|
31
|
+
discard it without stranding it. `cancelled` rather than `success`,
|
|
32
|
+
because nothing was measured -- a 0 ms success would be averaged in as a
|
|
33
|
+
real and very fast call -- and it is not an error, so error rates are
|
|
34
|
+
untouched.
|
|
35
|
+
* `start_span` takes `announce=False` for a span opened speculatively, and
|
|
36
|
+
the boto3 interceptor uses it. A span that may turn out to belong to
|
|
37
|
+
another layer is no longer announced at open, so discarding it costs
|
|
38
|
+
nothing and leaves no `cancelled` row on every trace.
|
|
39
|
+
|
|
40
|
+
The cost of the second change: a bare Bedrock call made through boto3
|
|
41
|
+
*outside* a Strands agent is no longer visible while it runs, only once it
|
|
42
|
+
completes. Inside Strands -- the common case, and the one that was broken --
|
|
43
|
+
the agent span above it keeps in-progress visibility.
|
|
44
|
+
|
|
45
|
+
### Repairing existing data
|
|
46
|
+
|
|
47
|
+
Runs already stuck cannot fix themselves: their provisional rows will never be
|
|
48
|
+
superseded. Write the retraction the fixed code would have written -- a
|
|
49
|
+
`cancelled` row at `row_version` 2147483647 with the same `(project_id,
|
|
50
|
+
trace_id, start_time, span_id)` -- for every span matching
|
|
51
|
+
|
|
52
|
+
```sql
|
|
53
|
+
SELECT * FROM trace_events FINAL
|
|
54
|
+
WHERE status NOT IN ('success','error','timeout','cancelled')
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
then re-project the affected runs. On the deployment above this moved 18 runs
|
|
58
|
+
from `running` to `success` and left no open spans.
|
|
59
|
+
|
|
60
|
+
## 0.1.1
|
|
61
|
+
|
|
62
|
+
**Upgrading from 0.1.0 requires a collector upgrade first.** See *Compatibility*
|
|
63
|
+
below — this is not optional, and getting the order wrong loses data.
|
|
64
|
+
|
|
65
|
+
### Added
|
|
66
|
+
|
|
67
|
+
- **Spans are sent when they start, not only when they finish.** Before this a
|
|
68
|
+
span reached Rootmark only from `end_span`, so anything in flight was simply
|
|
69
|
+
absent — and a step paused for human review was indistinguishable from one
|
|
70
|
+
wedged against a model that had stopped answering. A run is now visible from
|
|
71
|
+
the moment its first span opens.
|
|
72
|
+
- `rootmark.mark_waiting(span, reason)` — say a span is deliberately not
|
|
73
|
+
working, and what it is waiting on. `reason` is free text in the caller's own
|
|
74
|
+
vocabulary (`"human_review"`, `"rate_limit"`, `"upstream"`); the SDK does not
|
|
75
|
+
fix the set, because encoding one application's review workflow into the
|
|
76
|
+
schema would make it everyone's.
|
|
77
|
+
- `heartbeat=` on `rootmark.span()` — re-announce a long span on an interval so
|
|
78
|
+
a killed process can be told apart from a slow one. Opt-in per span, because
|
|
79
|
+
a heartbeat on a 200 ms call is volume spent to say nothing. Intervals below
|
|
80
|
+
five seconds are raised to five rather than refused.
|
|
81
|
+
- `step_key` / `step_kind` on spans, and `SpanEmitter.set_step()` for callers
|
|
82
|
+
that learn what a step is only after starting it. `step_key` is a stable
|
|
83
|
+
identity for which logical step a span is; `step_kind` is what it was for.
|
|
84
|
+
A kind the SDK does not recognise is kept, not dropped.
|
|
85
|
+
- `SpanStatus` gains `running`, `waiting` and `cancelled`, plus an `is_terminal`
|
|
86
|
+
property. The first three values are unchanged.
|
|
87
|
+
|
|
88
|
+
### Changed
|
|
89
|
+
|
|
90
|
+
- **`TraceEvent` carries `row_version` and `waiting_reason`.** `row_version`
|
|
91
|
+
orders the several writes a span now produces so the collector can keep the
|
|
92
|
+
latest: start is 1, heartbeats climb, the end row takes a sentinel above every
|
|
93
|
+
possible heartbeat. A rank rather than a timestamp, because rows arrive from
|
|
94
|
+
different processes over an at-least-once channel and a clock cannot order
|
|
95
|
+
them.
|
|
96
|
+
- **Bedrock guardrail traces no longer include `modelOutput`**, and the size cap
|
|
97
|
+
rose from 4 KB to 16 KB. Bedrock echoes the model's whole answer back inside
|
|
98
|
+
the guardrail trace; on a long answer that alone overran the old cap, so the
|
|
99
|
+
payload was stored as a truncated string and the `inputAssessment` naming the
|
|
100
|
+
policy that fired was never reached. A real deployment lost a `PROMPT_ATTACK`
|
|
101
|
+
block that way. The removal is recorded under `rootmarkOmitted`, so a reader
|
|
102
|
+
can tell a pruned trace from one Bedrock never sent the key for.
|
|
103
|
+
**If you read `extra.bedrock_guardrail_trace` expecting `modelOutput`, read
|
|
104
|
+
the model's response from the span's outputs instead.**
|
|
105
|
+
- A non-terminal span reports `duration_ms` of 0 and an `end_time` equal to its
|
|
106
|
+
`start_time`. **Read the status, not the duration**, to decide whether a span
|
|
107
|
+
has finished — a 0 ms span is otherwise a plausible-looking lie.
|
|
108
|
+
|
|
109
|
+
### Compatibility
|
|
110
|
+
|
|
111
|
+
| | |
|
|
112
|
+
|---|---|
|
|
113
|
+
| Collector | Needs ClickHouse migrations `007`–`009`. Upgrade it **before** the SDK. |
|
|
114
|
+
| Python | 3.12+, unchanged |
|
|
115
|
+
| Wire format | Additive; `row_version` and `waiting_reason` are new fields |
|
|
116
|
+
|
|
117
|
+
A collector older than this release deduplicates buffered spans on
|
|
118
|
+
`(project_id, span_id)` and keeps the **first** row it sees. With a 0.1.1
|
|
119
|
+
client that first row is the *start* row, so the end row — the one carrying the
|
|
120
|
+
result, the duration and the token counts — is discarded. Runs appear and never
|
|
121
|
+
finish. Where the two rows land in different batches instead, the old storage
|
|
122
|
+
engine has no version column to order them by and the survivor is undefined.
|
|
123
|
+
|
|
124
|
+
Neither failure reports an error. Upgrade the collector first, or stay on 0.1.0.
|
|
125
|
+
|
|
126
|
+
### Cost
|
|
127
|
+
|
|
128
|
+
Each span is now sent at least twice, so **a run produces roughly twice the
|
|
129
|
+
rows** it did on 0.1.0, plus one per heartbeat where heartbeats are enabled.
|
|
130
|
+
The collector collapses the pair when it merges, so stored size settles back;
|
|
131
|
+
what doubles is what crosses the network and passes through ingest. There is no
|
|
132
|
+
flag to turn it off — the alternative is being blind to work in progress, which
|
|
133
|
+
is the whole point of the release.
|
|
134
|
+
|
|
135
|
+
## 0.1.0
|
|
136
|
+
|
|
137
|
+
First public release. Span tree recording, framework instrumentation for
|
|
138
|
+
OpenAI, LangChain, CrewAI, Strands and Bedrock via botocore, batched background
|
|
139
|
+
delivery with a spill file and replay-safe batch identity, and
|
|
140
|
+
`suppress_payloads` for collectors outside the trust boundary.
|
|
141
|
+
|
|
142
|
+
No licence is declared, which means all rights reserved. That is unchanged in
|
|
143
|
+
0.1.1 and remains the thing to fix before this package is genuinely installable
|
|
144
|
+
by anyone outside the organisation.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: rootmark
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: Rootmark SDK -- agent instrumentation
|
|
5
5
|
Project-URL: Homepage, https://rootmark.dev
|
|
6
6
|
Project-URL: Documentation, https://rootmark.dev/docs/
|
|
@@ -51,6 +51,34 @@ pip install rootmark
|
|
|
51
51
|
|
|
52
52
|
Requires Python 3.12 or newer.
|
|
53
53
|
|
|
54
|
+
> **Upgrading from 0.1.0 needs a collector upgrade too.** Since 0.1.1 a span is
|
|
55
|
+
> sent when it *starts* as well as when it ends, so that work in progress is
|
|
56
|
+
> visible at all. A collector older than this release stores the first row it
|
|
57
|
+
> sees for a span and discards the rest — which is the end row, the one
|
|
58
|
+
> carrying the result and the duration. Runs would appear and never finish.
|
|
59
|
+
> Upgrade the collector first (it needs ClickHouse migrations `007`–`009`), or
|
|
60
|
+
> stay on 0.1.0.
|
|
61
|
+
|
|
62
|
+
> **0.1.1 is broken; use 0.1.2 or later.** In 0.1.1 a span that the SDK opened
|
|
63
|
+
> and then discarded was left looking like it was still running, which held its
|
|
64
|
+
> whole run open permanently. Any Bedrock call made through boto3 inside a
|
|
65
|
+
> Strands agent hits it, on every call.
|
|
66
|
+
|
|
67
|
+
## What changed in 0.1.1
|
|
68
|
+
|
|
69
|
+
- **Runs are visible while they are still going.** A span is recorded when it
|
|
70
|
+
starts, not only when it finishes.
|
|
71
|
+
- **`rootmark.mark_waiting(span, reason)`** distinguishes a step paused on a
|
|
72
|
+
human from one wedged against a model that stopped answering.
|
|
73
|
+
- **`heartbeat=`** on `rootmark.span()`, so a long step keeps saying it is alive.
|
|
74
|
+
- **Steps**: `step_key` and `step_kind` name which logical step a span is, so
|
|
75
|
+
the collector can aggregate the same step across runs.
|
|
76
|
+
- Bedrock guardrail traces no longer carry `modelOutput`, and the size cap rose
|
|
77
|
+
from 4 KB to 16 KB. A long answer used to overrun the cap and take the policy
|
|
78
|
+
assessment with it. Read the model's response from the span's outputs instead.
|
|
79
|
+
|
|
80
|
+
See `CHANGELOG.md` for the full list.
|
|
81
|
+
|
|
54
82
|
## Quickstart
|
|
55
83
|
|
|
56
84
|
```python
|
|
@@ -85,6 +113,49 @@ agent loop spawns, because the binding follows the async context.
|
|
|
85
113
|
`workflow_name` should not be `"default"`: that value lets framework
|
|
86
114
|
instrumentation override the name you bind per run with its own agent id.
|
|
87
115
|
|
|
116
|
+
## Naming the steps of a run
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
with rootmark.span("fetch_clauses", run_type=RunType.TOOL,
|
|
120
|
+
step_key="fetch_clauses", step_kind="retrieval"):
|
|
121
|
+
...
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
`step_key` is a stable identity for *which* logical step this is, constant
|
|
125
|
+
across runs; `step_kind` is what it was for (`retrieval`, `generation`,
|
|
126
|
+
`routing`, `review`, `tool`, `memory`). Together they let the collector answer
|
|
127
|
+
"which step is slow" without anyone reading individual traces.
|
|
128
|
+
|
|
129
|
+
Both are optional, and a kind the SDK does not recognise is kept rather than
|
|
130
|
+
dropped — your vocabulary is allowed to be ahead of ours. Derive `step_key`
|
|
131
|
+
from something stable: a key built from a timestamp or a request id fragments
|
|
132
|
+
its own aggregate, and nothing can detect that for you.
|
|
133
|
+
|
|
134
|
+
## Long-running steps
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
with rootmark.span("await_approval", run_type=RunType.TOOL, heartbeat=30) as span:
|
|
138
|
+
rootmark.mark_waiting(span, "human_review")
|
|
139
|
+
decision = wait_for_approval() # minutes, or hours
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
`mark_waiting` says the step is deliberately not working, and what it is
|
|
143
|
+
waiting on. The reason is free text in your own vocabulary — `human_review`,
|
|
144
|
+
`rate_limit`, `upstream` — and is shown back verbatim. It is the difference
|
|
145
|
+
between a run somebody has to investigate and one they can leave alone.
|
|
146
|
+
|
|
147
|
+
`heartbeat` re-announces the span every N seconds while it runs. Leave it unset
|
|
148
|
+
for anything that finishes quickly: a span is already visible from the moment
|
|
149
|
+
it starts, and a heartbeat for a 200 ms call is bandwidth spent to say nothing.
|
|
150
|
+
Set it on the long ones, where the useful question is not "is it running" but
|
|
151
|
+
"is anyone still on the other end" — without it, a step whose process was
|
|
152
|
+
killed looks exactly like one that is simply taking its time. Intervals below
|
|
153
|
+
five seconds are raised to five rather than refused.
|
|
154
|
+
|
|
155
|
+
**This roughly doubles the rows a run produces**, since each span is now sent
|
|
156
|
+
at least twice. The collector collapses the pair when it merges, so stored size
|
|
157
|
+
settles back; what doubles is what crosses the network.
|
|
158
|
+
|
|
88
159
|
## Framework support
|
|
89
160
|
|
|
90
161
|
The base install instruments nothing on its own beyond direct use. Install the
|
rootmark-0.1.2/README.md
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# Rootmark SDK
|
|
2
|
+
|
|
3
|
+
Agent instrumentation for [Rootmark](https://rootmark.dev) — observe, verify and
|
|
4
|
+
govern agent runs.
|
|
5
|
+
|
|
6
|
+
The SDK records what your agents actually did: the span tree of a run, the model
|
|
7
|
+
calls inside it, token counts, and the payloads you choose to keep. It sends
|
|
8
|
+
them to a Rootmark collector, which you can self-host.
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
pip install rootmark
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Requires Python 3.12 or newer.
|
|
15
|
+
|
|
16
|
+
> **Upgrading from 0.1.0 needs a collector upgrade too.** Since 0.1.1 a span is
|
|
17
|
+
> sent when it *starts* as well as when it ends, so that work in progress is
|
|
18
|
+
> visible at all. A collector older than this release stores the first row it
|
|
19
|
+
> sees for a span and discards the rest — which is the end row, the one
|
|
20
|
+
> carrying the result and the duration. Runs would appear and never finish.
|
|
21
|
+
> Upgrade the collector first (it needs ClickHouse migrations `007`–`009`), or
|
|
22
|
+
> stay on 0.1.0.
|
|
23
|
+
|
|
24
|
+
> **0.1.1 is broken; use 0.1.2 or later.** In 0.1.1 a span that the SDK opened
|
|
25
|
+
> and then discarded was left looking like it was still running, which held its
|
|
26
|
+
> whole run open permanently. Any Bedrock call made through boto3 inside a
|
|
27
|
+
> Strands agent hits it, on every call.
|
|
28
|
+
|
|
29
|
+
## What changed in 0.1.1
|
|
30
|
+
|
|
31
|
+
- **Runs are visible while they are still going.** A span is recorded when it
|
|
32
|
+
starts, not only when it finishes.
|
|
33
|
+
- **`rootmark.mark_waiting(span, reason)`** distinguishes a step paused on a
|
|
34
|
+
human from one wedged against a model that stopped answering.
|
|
35
|
+
- **`heartbeat=`** on `rootmark.span()`, so a long step keeps saying it is alive.
|
|
36
|
+
- **Steps**: `step_key` and `step_kind` name which logical step a span is, so
|
|
37
|
+
the collector can aggregate the same step across runs.
|
|
38
|
+
- Bedrock guardrail traces no longer carry `modelOutput`, and the size cap rose
|
|
39
|
+
from 4 KB to 16 KB. A long answer used to overrun the cap and take the policy
|
|
40
|
+
assessment with it. Read the model's response from the span's outputs instead.
|
|
41
|
+
|
|
42
|
+
See `CHANGELOG.md` for the full list.
|
|
43
|
+
|
|
44
|
+
## Quickstart
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
import rootmark
|
|
48
|
+
|
|
49
|
+
rootmark.init(
|
|
50
|
+
api_key="pk_...", # project API key
|
|
51
|
+
project_id="...", # UUID
|
|
52
|
+
api_key_id="...", # UUID
|
|
53
|
+
api_url="https://rootmark.example.com",
|
|
54
|
+
workflow_name="my-service", # not "default" -- see below
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
rootmark.patch() # instrument supported frameworks in place
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
That is the whole integration for the common case. `patch()` installs
|
|
61
|
+
instrumentation process-wide, so call it once at startup and before the first
|
|
62
|
+
agent invocation.
|
|
63
|
+
|
|
64
|
+
## Grouping and labelling runs
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
with rootmark.workflow_override("contract-review"):
|
|
68
|
+
with rootmark.bind_context(tenant_id="acme", session_id=session, tags=["surface:chat"]):
|
|
69
|
+
result = agent(prompt)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`bind_context` applies to everything that runs inside it, including tasks the
|
|
73
|
+
agent loop spawns, because the binding follows the async context.
|
|
74
|
+
|
|
75
|
+
`workflow_name` should not be `"default"`: that value lets framework
|
|
76
|
+
instrumentation override the name you bind per run with its own agent id.
|
|
77
|
+
|
|
78
|
+
## Naming the steps of a run
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
with rootmark.span("fetch_clauses", run_type=RunType.TOOL,
|
|
82
|
+
step_key="fetch_clauses", step_kind="retrieval"):
|
|
83
|
+
...
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`step_key` is a stable identity for *which* logical step this is, constant
|
|
87
|
+
across runs; `step_kind` is what it was for (`retrieval`, `generation`,
|
|
88
|
+
`routing`, `review`, `tool`, `memory`). Together they let the collector answer
|
|
89
|
+
"which step is slow" without anyone reading individual traces.
|
|
90
|
+
|
|
91
|
+
Both are optional, and a kind the SDK does not recognise is kept rather than
|
|
92
|
+
dropped — your vocabulary is allowed to be ahead of ours. Derive `step_key`
|
|
93
|
+
from something stable: a key built from a timestamp or a request id fragments
|
|
94
|
+
its own aggregate, and nothing can detect that for you.
|
|
95
|
+
|
|
96
|
+
## Long-running steps
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
with rootmark.span("await_approval", run_type=RunType.TOOL, heartbeat=30) as span:
|
|
100
|
+
rootmark.mark_waiting(span, "human_review")
|
|
101
|
+
decision = wait_for_approval() # minutes, or hours
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
`mark_waiting` says the step is deliberately not working, and what it is
|
|
105
|
+
waiting on. The reason is free text in your own vocabulary — `human_review`,
|
|
106
|
+
`rate_limit`, `upstream` — and is shown back verbatim. It is the difference
|
|
107
|
+
between a run somebody has to investigate and one they can leave alone.
|
|
108
|
+
|
|
109
|
+
`heartbeat` re-announces the span every N seconds while it runs. Leave it unset
|
|
110
|
+
for anything that finishes quickly: a span is already visible from the moment
|
|
111
|
+
it starts, and a heartbeat for a 200 ms call is bandwidth spent to say nothing.
|
|
112
|
+
Set it on the long ones, where the useful question is not "is it running" but
|
|
113
|
+
"is anyone still on the other end" — without it, a step whose process was
|
|
114
|
+
killed looks exactly like one that is simply taking its time. Intervals below
|
|
115
|
+
five seconds are raised to five rather than refused.
|
|
116
|
+
|
|
117
|
+
**This roughly doubles the rows a run produces**, since each span is now sent
|
|
118
|
+
at least twice. The collector collapses the pair when it merges, so stored size
|
|
119
|
+
settles back; what doubles is what crosses the network.
|
|
120
|
+
|
|
121
|
+
## Framework support
|
|
122
|
+
|
|
123
|
+
The base install instruments nothing on its own beyond direct use. Install the
|
|
124
|
+
extra for what you actually run:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
pip install "rootmark[openai]" # OpenAI 1.x and 2.x
|
|
128
|
+
pip install "rootmark[langchain]" # LangChain callback handler
|
|
129
|
+
pip install "rootmark[crewai]" # CrewAI event listeners
|
|
130
|
+
pip install "rootmark[strands]" # Strands agents / AWS Bedrock
|
|
131
|
+
pip install "rootmark[boto3]" # Bedrock via botocore, no code change
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
For boto3, a session built by hand rather than the default one needs
|
|
135
|
+
`rootmark.patch_session(session)` — the botocore hooks only reach the default
|
|
136
|
+
session otherwise.
|
|
137
|
+
|
|
138
|
+
## Delivery
|
|
139
|
+
|
|
140
|
+
Spans are batched on a background thread and flushed on a timer, so nothing
|
|
141
|
+
blocks a request. A failed export is written to a spill file and retried,
|
|
142
|
+
keeping its batch identity so the collector can recognise a replay rather than
|
|
143
|
+
double-count it. Ordering is preserved: a batch that cannot be delivered holds
|
|
144
|
+
back the ones behind it.
|
|
145
|
+
|
|
146
|
+
Point `spill_file` somewhere writable. The default is relative to the working
|
|
147
|
+
directory, which in a container is frequently owned by root while the process
|
|
148
|
+
runs as someone else — and a spill file that cannot be written is buffering you
|
|
149
|
+
silently do not have.
|
|
150
|
+
|
|
151
|
+
Call `rootmark.flush()` before a process exits if you want outstanding spans
|
|
152
|
+
delivered, and `rootmark.shutdown()` to stop the processor.
|
|
153
|
+
|
|
154
|
+
## Keeping payloads out
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
rootmark.init(..., suppress_payloads=True)
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Spans are still recorded — timings, token counts, the shape of the run — but
|
|
161
|
+
prompt and completion text is not sent. Use this when the collector sits outside
|
|
162
|
+
the trust boundary that the data does not leave.
|
|
163
|
+
|
|
164
|
+
## Links
|
|
165
|
+
|
|
166
|
+
- Documentation: <https://rootmark.dev/docs/>
|
|
167
|
+
- Source: <https://github.com/IvanAdactic/rootmark>
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "rootmark"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.2"
|
|
4
4
|
description = "Rootmark SDK -- agent instrumentation"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.12"
|
|
7
|
-
# No licence declared
|
|
7
|
+
# No licence declared as of 0.1.2; to be added in a later release. Without
|
|
8
|
+
# one the package defaults to all rights reserved -- see the 0.1.0 release
|
|
9
|
+
# commit (329e16c) for the reasoning and why it still needs revisiting.
|
|
8
10
|
keywords = ["agents", "observability", "tracing", "llm", "instrumentation"]
|
|
9
11
|
classifiers = [
|
|
10
12
|
"Development Status :: 4 - Beta",
|