rootmark 0.0.1__tar.gz → 0.1.1__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.
Files changed (54) hide show
  1. rootmark-0.1.1/.gitignore +37 -0
  2. rootmark-0.1.1/CHANGELOG.md +90 -0
  3. rootmark-0.1.1/PKG-INFO +200 -0
  4. rootmark-0.1.1/README.md +162 -0
  5. rootmark-0.1.1/pyproject.toml +58 -0
  6. rootmark-0.1.1/src/rootmark/__init__.py +300 -0
  7. rootmark-0.1.1/src/rootmark/audit/__init__.py +5 -0
  8. rootmark-0.1.1/src/rootmark/audit/hashing.py +20 -0
  9. rootmark-0.1.1/src/rootmark/config.py +267 -0
  10. rootmark-0.1.1/src/rootmark/core/__init__.py +10 -0
  11. rootmark-0.1.1/src/rootmark/core/emitter.py +650 -0
  12. rootmark-0.1.1/src/rootmark/core/lifecycle.py +94 -0
  13. rootmark-0.1.1/src/rootmark/core/processor.py +383 -0
  14. rootmark-0.1.1/src/rootmark/core/step.py +72 -0
  15. rootmark-0.1.1/src/rootmark/patches/__init__.py +96 -0
  16. rootmark-0.1.1/src/rootmark/patches/_bedrock_helpers.py +207 -0
  17. rootmark-0.1.1/src/rootmark/patches/agentcore.py +737 -0
  18. rootmark-0.1.1/src/rootmark/patches/boto3_interceptor.py +624 -0
  19. rootmark-0.1.1/src/rootmark/patches/crewai.py +480 -0
  20. rootmark-0.1.1/src/rootmark/patches/langchain.py +272 -0
  21. rootmark-0.1.1/src/rootmark/patches/openai.py +185 -0
  22. rootmark-0.1.1/src/rootmark/patches/strands.py +685 -0
  23. rootmark-0.1.1/src/rootmark/patches/strands_multiagent.py +576 -0
  24. rootmark-0.1.1/src/rootmark/patches/strands_session.py +217 -0
  25. rootmark-0.1.1/src/rootmark/schema/__init__.py +8 -0
  26. rootmark-0.1.1/src/rootmark/schema/trace_event.py +129 -0
  27. rootmark-0.1.1/tests/core/test_emitter_bind_context.py +130 -0
  28. rootmark-0.1.1/tests/core/test_emitter_session.py +106 -0
  29. rootmark-0.1.1/tests/core/test_processor_envelope.py +285 -0
  30. rootmark-0.1.1/tests/integration/conftest.py +59 -0
  31. rootmark-0.1.1/tests/integration/test_strands_e2e.py +290 -0
  32. rootmark-0.1.1/tests/patches/__init__.py +1 -0
  33. rootmark-0.1.1/tests/patches/test_agentcore.py +545 -0
  34. rootmark-0.1.1/tests/patches/test_strands.py +300 -0
  35. rootmark-0.1.1/tests/test_boto3_interceptor.py +1282 -0
  36. rootmark-0.1.1/tests/test_config_override.py +229 -0
  37. rootmark-0.1.1/tests/test_crewai_patch.py +182 -0
  38. rootmark-0.1.1/tests/test_langchain_handler.py +99 -0
  39. rootmark-0.1.1/tests/test_roundtrip_ingestion.py +127 -0
  40. rootmark-0.1.1/tests/test_span_lifecycle.py +255 -0
  41. rootmark-0.1.1/tests/test_span_tags.py +218 -0
  42. rootmark-0.1.1/tests/test_step_contract.py +53 -0
  43. rootmark-0.1.1/tests/test_strands_agent_live.py +320 -0
  44. rootmark-0.1.1/tests/test_strands_multiagent.py +472 -0
  45. rootmark-0.1.1/tests/test_strands_multiagent_live.py +404 -0
  46. rootmark-0.1.1/tests/test_strands_patch.py +676 -0
  47. rootmark-0.1.1/tests/test_strands_session.py +307 -0
  48. rootmark-0.1.1/tests/test_suppress_payloads.py +202 -0
  49. rootmark-0.1.1/tests/test_trace_and_span.py +384 -0
  50. rootmark-0.0.1/LICENSE +0 -21
  51. rootmark-0.0.1/PKG-INFO +0 -22
  52. rootmark-0.0.1/README.md +0 -9
  53. rootmark-0.0.1/pyproject.toml +0 -20
  54. rootmark-0.0.1/src/rootmark/__init__.py +0 -12
@@ -0,0 +1,37 @@
1
+ venv/
2
+ .venv/
3
+ __pycache__/
4
+ *.pyc
5
+ .mypy_cache/
6
+ dist/
7
+ .pytest_cache/
8
+ .coverage
9
+ .tox/
10
+ .ruff_cache/
11
+ .env
12
+
13
+ # MkDocs
14
+ site/
15
+
16
+
17
+ # migration scratch (not part of the project)
18
+ /_to_delete_git_1786975500/
19
+ /_pip-reserve-dist/
20
+
21
+ # SDK spill-over buffers: runtime state, may contain trace payloads
22
+ .rootmark_spillover.jsonl
23
+ *.rootmark_spillover.jsonl
24
+ .rootmark_spillover.jsonl.tmp
25
+
26
+ # local CLI credentials
27
+ .rootmark.env
28
+
29
+ # fixed dev credentials for `make seed-project` (project ids + raw API key)
30
+ .env.seed
31
+
32
+ # scratch / build output
33
+ /artifacts/
34
+ /.tmp_g7_cli/
35
+
36
+ # TS build output
37
+ *.tsbuildinfo
@@ -0,0 +1,90 @@
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.1
7
+
8
+ **Upgrading from 0.1.0 requires a collector upgrade first.** See *Compatibility*
9
+ below — this is not optional, and getting the order wrong loses data.
10
+
11
+ ### Added
12
+
13
+ - **Spans are sent when they start, not only when they finish.** Before this a
14
+ span reached Rootmark only from `end_span`, so anything in flight was simply
15
+ absent — and a step paused for human review was indistinguishable from one
16
+ wedged against a model that had stopped answering. A run is now visible from
17
+ the moment its first span opens.
18
+ - `rootmark.mark_waiting(span, reason)` — say a span is deliberately not
19
+ working, and what it is waiting on. `reason` is free text in the caller's own
20
+ vocabulary (`"human_review"`, `"rate_limit"`, `"upstream"`); the SDK does not
21
+ fix the set, because encoding one application's review workflow into the
22
+ schema would make it everyone's.
23
+ - `heartbeat=` on `rootmark.span()` — re-announce a long span on an interval so
24
+ a killed process can be told apart from a slow one. Opt-in per span, because
25
+ a heartbeat on a 200 ms call is volume spent to say nothing. Intervals below
26
+ five seconds are raised to five rather than refused.
27
+ - `step_key` / `step_kind` on spans, and `SpanEmitter.set_step()` for callers
28
+ that learn what a step is only after starting it. `step_key` is a stable
29
+ identity for which logical step a span is; `step_kind` is what it was for.
30
+ A kind the SDK does not recognise is kept, not dropped.
31
+ - `SpanStatus` gains `running`, `waiting` and `cancelled`, plus an `is_terminal`
32
+ property. The first three values are unchanged.
33
+
34
+ ### Changed
35
+
36
+ - **`TraceEvent` carries `row_version` and `waiting_reason`.** `row_version`
37
+ orders the several writes a span now produces so the collector can keep the
38
+ latest: start is 1, heartbeats climb, the end row takes a sentinel above every
39
+ possible heartbeat. A rank rather than a timestamp, because rows arrive from
40
+ different processes over an at-least-once channel and a clock cannot order
41
+ them.
42
+ - **Bedrock guardrail traces no longer include `modelOutput`**, and the size cap
43
+ rose from 4 KB to 16 KB. Bedrock echoes the model's whole answer back inside
44
+ the guardrail trace; on a long answer that alone overran the old cap, so the
45
+ payload was stored as a truncated string and the `inputAssessment` naming the
46
+ policy that fired was never reached. A real deployment lost a `PROMPT_ATTACK`
47
+ block that way. The removal is recorded under `rootmarkOmitted`, so a reader
48
+ can tell a pruned trace from one Bedrock never sent the key for.
49
+ **If you read `extra.bedrock_guardrail_trace` expecting `modelOutput`, read
50
+ the model's response from the span's outputs instead.**
51
+ - A non-terminal span reports `duration_ms` of 0 and an `end_time` equal to its
52
+ `start_time`. **Read the status, not the duration**, to decide whether a span
53
+ has finished — a 0 ms span is otherwise a plausible-looking lie.
54
+
55
+ ### Compatibility
56
+
57
+ | | |
58
+ |---|---|
59
+ | Collector | Needs ClickHouse migrations `007`–`009`. Upgrade it **before** the SDK. |
60
+ | Python | 3.12+, unchanged |
61
+ | Wire format | Additive; `row_version` and `waiting_reason` are new fields |
62
+
63
+ A collector older than this release deduplicates buffered spans on
64
+ `(project_id, span_id)` and keeps the **first** row it sees. With a 0.1.1
65
+ client that first row is the *start* row, so the end row — the one carrying the
66
+ result, the duration and the token counts — is discarded. Runs appear and never
67
+ finish. Where the two rows land in different batches instead, the old storage
68
+ engine has no version column to order them by and the survivor is undefined.
69
+
70
+ Neither failure reports an error. Upgrade the collector first, or stay on 0.1.0.
71
+
72
+ ### Cost
73
+
74
+ Each span is now sent at least twice, so **a run produces roughly twice the
75
+ rows** it did on 0.1.0, plus one per heartbeat where heartbeats are enabled.
76
+ The collector collapses the pair when it merges, so stored size settles back;
77
+ what doubles is what crosses the network and passes through ingest. There is no
78
+ flag to turn it off — the alternative is being blind to work in progress, which
79
+ is the whole point of the release.
80
+
81
+ ## 0.1.0
82
+
83
+ First public release. Span tree recording, framework instrumentation for
84
+ OpenAI, LangChain, CrewAI, Strands and Bedrock via botocore, batched background
85
+ delivery with a spill file and replay-safe batch identity, and
86
+ `suppress_payloads` for collectors outside the trust boundary.
87
+
88
+ No licence is declared, which means all rights reserved. That is unchanged in
89
+ 0.1.1 and remains the thing to fix before this package is genuinely installable
90
+ by anyone outside the organisation.
@@ -0,0 +1,200 @@
1
+ Metadata-Version: 2.5
2
+ Name: rootmark
3
+ Version: 0.1.1
4
+ Summary: Rootmark SDK -- agent instrumentation
5
+ Project-URL: Homepage, https://rootmark.dev
6
+ Project-URL: Documentation, https://rootmark.dev/docs/
7
+ Project-URL: Source, https://github.com/IvanAdactic/rootmark
8
+ Keywords: agents,instrumentation,llm,observability,tracing
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Programming Language :: Python :: 3.13
14
+ Classifier: Programming Language :: Python :: 3.14
15
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
16
+ Classifier: Topic :: System :: Monitoring
17
+ Classifier: Typing :: Typed
18
+ Requires-Python: >=3.12
19
+ Requires-Dist: httpx>=0.28.0
20
+ Requires-Dist: pydantic>=2.10.0
21
+ Provides-Extra: boto3
22
+ Requires-Dist: boto3>=1.35.0; extra == 'boto3'
23
+ Provides-Extra: crewai
24
+ Requires-Dist: crewai<2,>=0.86; extra == 'crewai'
25
+ Provides-Extra: dev
26
+ Requires-Dist: crewai<2,>=0.86; extra == 'dev'
27
+ Requires-Dist: langchain-core<2,>=0.3; extra == 'dev'
28
+ Requires-Dist: openai<3,>=1.40.0; extra == 'dev'
29
+ Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
30
+ Requires-Dist: pytest>=8.0; extra == 'dev'
31
+ Provides-Extra: langchain
32
+ Requires-Dist: langchain-core<2,>=0.3; extra == 'langchain'
33
+ Provides-Extra: openai
34
+ Requires-Dist: openai<3,>=1.40.0; extra == 'openai'
35
+ Provides-Extra: strands
36
+ Requires-Dist: strands-agents>=1.26.0; extra == 'strands'
37
+ Description-Content-Type: text/markdown
38
+
39
+ # Rootmark SDK
40
+
41
+ Agent instrumentation for [Rootmark](https://rootmark.dev) — observe, verify and
42
+ govern agent runs.
43
+
44
+ The SDK records what your agents actually did: the span tree of a run, the model
45
+ calls inside it, token counts, and the payloads you choose to keep. It sends
46
+ them to a Rootmark collector, which you can self-host.
47
+
48
+ ```bash
49
+ pip install rootmark
50
+ ```
51
+
52
+ Requires Python 3.12 or newer.
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
+ ## What changed in 0.1.1
63
+
64
+ - **Runs are visible while they are still going.** A span is recorded when it
65
+ starts, not only when it finishes.
66
+ - **`rootmark.mark_waiting(span, reason)`** distinguishes a step paused on a
67
+ human from one wedged against a model that stopped answering.
68
+ - **`heartbeat=`** on `rootmark.span()`, so a long step keeps saying it is alive.
69
+ - **Steps**: `step_key` and `step_kind` name which logical step a span is, so
70
+ the collector can aggregate the same step across runs.
71
+ - Bedrock guardrail traces no longer carry `modelOutput`, and the size cap rose
72
+ from 4 KB to 16 KB. A long answer used to overrun the cap and take the policy
73
+ assessment with it. Read the model's response from the span's outputs instead.
74
+
75
+ See `CHANGELOG.md` for the full list.
76
+
77
+ ## Quickstart
78
+
79
+ ```python
80
+ import rootmark
81
+
82
+ rootmark.init(
83
+ api_key="pk_...", # project API key
84
+ project_id="...", # UUID
85
+ api_key_id="...", # UUID
86
+ api_url="https://rootmark.example.com",
87
+ workflow_name="my-service", # not "default" -- see below
88
+ )
89
+
90
+ rootmark.patch() # instrument supported frameworks in place
91
+ ```
92
+
93
+ That is the whole integration for the common case. `patch()` installs
94
+ instrumentation process-wide, so call it once at startup and before the first
95
+ agent invocation.
96
+
97
+ ## Grouping and labelling runs
98
+
99
+ ```python
100
+ with rootmark.workflow_override("contract-review"):
101
+ with rootmark.bind_context(tenant_id="acme", session_id=session, tags=["surface:chat"]):
102
+ result = agent(prompt)
103
+ ```
104
+
105
+ `bind_context` applies to everything that runs inside it, including tasks the
106
+ agent loop spawns, because the binding follows the async context.
107
+
108
+ `workflow_name` should not be `"default"`: that value lets framework
109
+ instrumentation override the name you bind per run with its own agent id.
110
+
111
+ ## Naming the steps of a run
112
+
113
+ ```python
114
+ with rootmark.span("fetch_clauses", run_type=RunType.TOOL,
115
+ step_key="fetch_clauses", step_kind="retrieval"):
116
+ ...
117
+ ```
118
+
119
+ `step_key` is a stable identity for *which* logical step this is, constant
120
+ across runs; `step_kind` is what it was for (`retrieval`, `generation`,
121
+ `routing`, `review`, `tool`, `memory`). Together they let the collector answer
122
+ "which step is slow" without anyone reading individual traces.
123
+
124
+ Both are optional, and a kind the SDK does not recognise is kept rather than
125
+ dropped — your vocabulary is allowed to be ahead of ours. Derive `step_key`
126
+ from something stable: a key built from a timestamp or a request id fragments
127
+ its own aggregate, and nothing can detect that for you.
128
+
129
+ ## Long-running steps
130
+
131
+ ```python
132
+ with rootmark.span("await_approval", run_type=RunType.TOOL, heartbeat=30) as span:
133
+ rootmark.mark_waiting(span, "human_review")
134
+ decision = wait_for_approval() # minutes, or hours
135
+ ```
136
+
137
+ `mark_waiting` says the step is deliberately not working, and what it is
138
+ waiting on. The reason is free text in your own vocabulary — `human_review`,
139
+ `rate_limit`, `upstream` — and is shown back verbatim. It is the difference
140
+ between a run somebody has to investigate and one they can leave alone.
141
+
142
+ `heartbeat` re-announces the span every N seconds while it runs. Leave it unset
143
+ for anything that finishes quickly: a span is already visible from the moment
144
+ it starts, and a heartbeat for a 200 ms call is bandwidth spent to say nothing.
145
+ Set it on the long ones, where the useful question is not "is it running" but
146
+ "is anyone still on the other end" — without it, a step whose process was
147
+ killed looks exactly like one that is simply taking its time. Intervals below
148
+ five seconds are raised to five rather than refused.
149
+
150
+ **This roughly doubles the rows a run produces**, since each span is now sent
151
+ at least twice. The collector collapses the pair when it merges, so stored size
152
+ settles back; what doubles is what crosses the network.
153
+
154
+ ## Framework support
155
+
156
+ The base install instruments nothing on its own beyond direct use. Install the
157
+ extra for what you actually run:
158
+
159
+ ```bash
160
+ pip install "rootmark[openai]" # OpenAI 1.x and 2.x
161
+ pip install "rootmark[langchain]" # LangChain callback handler
162
+ pip install "rootmark[crewai]" # CrewAI event listeners
163
+ pip install "rootmark[strands]" # Strands agents / AWS Bedrock
164
+ pip install "rootmark[boto3]" # Bedrock via botocore, no code change
165
+ ```
166
+
167
+ For boto3, a session built by hand rather than the default one needs
168
+ `rootmark.patch_session(session)` — the botocore hooks only reach the default
169
+ session otherwise.
170
+
171
+ ## Delivery
172
+
173
+ Spans are batched on a background thread and flushed on a timer, so nothing
174
+ blocks a request. A failed export is written to a spill file and retried,
175
+ keeping its batch identity so the collector can recognise a replay rather than
176
+ double-count it. Ordering is preserved: a batch that cannot be delivered holds
177
+ back the ones behind it.
178
+
179
+ Point `spill_file` somewhere writable. The default is relative to the working
180
+ directory, which in a container is frequently owned by root while the process
181
+ runs as someone else — and a spill file that cannot be written is buffering you
182
+ silently do not have.
183
+
184
+ Call `rootmark.flush()` before a process exits if you want outstanding spans
185
+ delivered, and `rootmark.shutdown()` to stop the processor.
186
+
187
+ ## Keeping payloads out
188
+
189
+ ```python
190
+ rootmark.init(..., suppress_payloads=True)
191
+ ```
192
+
193
+ Spans are still recorded — timings, token counts, the shape of the run — but
194
+ prompt and completion text is not sent. Use this when the collector sits outside
195
+ the trust boundary that the data does not leave.
196
+
197
+ ## Links
198
+
199
+ - Documentation: <https://rootmark.dev/docs/>
200
+ - Source: <https://github.com/IvanAdactic/rootmark>
@@ -0,0 +1,162 @@
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
+ ## What changed in 0.1.1
25
+
26
+ - **Runs are visible while they are still going.** A span is recorded when it
27
+ starts, not only when it finishes.
28
+ - **`rootmark.mark_waiting(span, reason)`** distinguishes a step paused on a
29
+ human from one wedged against a model that stopped answering.
30
+ - **`heartbeat=`** on `rootmark.span()`, so a long step keeps saying it is alive.
31
+ - **Steps**: `step_key` and `step_kind` name which logical step a span is, so
32
+ the collector can aggregate the same step across runs.
33
+ - Bedrock guardrail traces no longer carry `modelOutput`, and the size cap rose
34
+ from 4 KB to 16 KB. A long answer used to overrun the cap and take the policy
35
+ assessment with it. Read the model's response from the span's outputs instead.
36
+
37
+ See `CHANGELOG.md` for the full list.
38
+
39
+ ## Quickstart
40
+
41
+ ```python
42
+ import rootmark
43
+
44
+ rootmark.init(
45
+ api_key="pk_...", # project API key
46
+ project_id="...", # UUID
47
+ api_key_id="...", # UUID
48
+ api_url="https://rootmark.example.com",
49
+ workflow_name="my-service", # not "default" -- see below
50
+ )
51
+
52
+ rootmark.patch() # instrument supported frameworks in place
53
+ ```
54
+
55
+ That is the whole integration for the common case. `patch()` installs
56
+ instrumentation process-wide, so call it once at startup and before the first
57
+ agent invocation.
58
+
59
+ ## Grouping and labelling runs
60
+
61
+ ```python
62
+ with rootmark.workflow_override("contract-review"):
63
+ with rootmark.bind_context(tenant_id="acme", session_id=session, tags=["surface:chat"]):
64
+ result = agent(prompt)
65
+ ```
66
+
67
+ `bind_context` applies to everything that runs inside it, including tasks the
68
+ agent loop spawns, because the binding follows the async context.
69
+
70
+ `workflow_name` should not be `"default"`: that value lets framework
71
+ instrumentation override the name you bind per run with its own agent id.
72
+
73
+ ## Naming the steps of a run
74
+
75
+ ```python
76
+ with rootmark.span("fetch_clauses", run_type=RunType.TOOL,
77
+ step_key="fetch_clauses", step_kind="retrieval"):
78
+ ...
79
+ ```
80
+
81
+ `step_key` is a stable identity for *which* logical step this is, constant
82
+ across runs; `step_kind` is what it was for (`retrieval`, `generation`,
83
+ `routing`, `review`, `tool`, `memory`). Together they let the collector answer
84
+ "which step is slow" without anyone reading individual traces.
85
+
86
+ Both are optional, and a kind the SDK does not recognise is kept rather than
87
+ dropped — your vocabulary is allowed to be ahead of ours. Derive `step_key`
88
+ from something stable: a key built from a timestamp or a request id fragments
89
+ its own aggregate, and nothing can detect that for you.
90
+
91
+ ## Long-running steps
92
+
93
+ ```python
94
+ with rootmark.span("await_approval", run_type=RunType.TOOL, heartbeat=30) as span:
95
+ rootmark.mark_waiting(span, "human_review")
96
+ decision = wait_for_approval() # minutes, or hours
97
+ ```
98
+
99
+ `mark_waiting` says the step is deliberately not working, and what it is
100
+ waiting on. The reason is free text in your own vocabulary — `human_review`,
101
+ `rate_limit`, `upstream` — and is shown back verbatim. It is the difference
102
+ between a run somebody has to investigate and one they can leave alone.
103
+
104
+ `heartbeat` re-announces the span every N seconds while it runs. Leave it unset
105
+ for anything that finishes quickly: a span is already visible from the moment
106
+ it starts, and a heartbeat for a 200 ms call is bandwidth spent to say nothing.
107
+ Set it on the long ones, where the useful question is not "is it running" but
108
+ "is anyone still on the other end" — without it, a step whose process was
109
+ killed looks exactly like one that is simply taking its time. Intervals below
110
+ five seconds are raised to five rather than refused.
111
+
112
+ **This roughly doubles the rows a run produces**, since each span is now sent
113
+ at least twice. The collector collapses the pair when it merges, so stored size
114
+ settles back; what doubles is what crosses the network.
115
+
116
+ ## Framework support
117
+
118
+ The base install instruments nothing on its own beyond direct use. Install the
119
+ extra for what you actually run:
120
+
121
+ ```bash
122
+ pip install "rootmark[openai]" # OpenAI 1.x and 2.x
123
+ pip install "rootmark[langchain]" # LangChain callback handler
124
+ pip install "rootmark[crewai]" # CrewAI event listeners
125
+ pip install "rootmark[strands]" # Strands agents / AWS Bedrock
126
+ pip install "rootmark[boto3]" # Bedrock via botocore, no code change
127
+ ```
128
+
129
+ For boto3, a session built by hand rather than the default one needs
130
+ `rootmark.patch_session(session)` — the botocore hooks only reach the default
131
+ session otherwise.
132
+
133
+ ## Delivery
134
+
135
+ Spans are batched on a background thread and flushed on a timer, so nothing
136
+ blocks a request. A failed export is written to a spill file and retried,
137
+ keeping its batch identity so the collector can recognise a replay rather than
138
+ double-count it. Ordering is preserved: a batch that cannot be delivered holds
139
+ back the ones behind it.
140
+
141
+ Point `spill_file` somewhere writable. The default is relative to the working
142
+ directory, which in a container is frequently owned by root while the process
143
+ runs as someone else — and a spill file that cannot be written is buffering you
144
+ silently do not have.
145
+
146
+ Call `rootmark.flush()` before a process exits if you want outstanding spans
147
+ delivered, and `rootmark.shutdown()` to stop the processor.
148
+
149
+ ## Keeping payloads out
150
+
151
+ ```python
152
+ rootmark.init(..., suppress_payloads=True)
153
+ ```
154
+
155
+ Spans are still recorded — timings, token counts, the shape of the run — but
156
+ prompt and completion text is not sent. Use this when the collector sits outside
157
+ the trust boundary that the data does not leave.
158
+
159
+ ## Links
160
+
161
+ - Documentation: <https://rootmark.dev/docs/>
162
+ - Source: <https://github.com/IvanAdactic/rootmark>
@@ -0,0 +1,58 @@
1
+ [project]
2
+ name = "rootmark"
3
+ version = "0.1.1"
4
+ description = "Rootmark SDK -- agent instrumentation"
5
+ readme = "README.md"
6
+ requires-python = ">=3.12"
7
+ # No licence declared as of 0.1.1; 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.
10
+ keywords = ["agents", "observability", "tracing", "llm", "instrumentation"]
11
+ classifiers = [
12
+ "Development Status :: 4 - Beta",
13
+ "Intended Audience :: Developers",
14
+ "Programming Language :: Python :: 3",
15
+ "Programming Language :: Python :: 3.12",
16
+ "Programming Language :: Python :: 3.13",
17
+ "Programming Language :: Python :: 3.14",
18
+ "Topic :: Software Development :: Libraries :: Python Modules",
19
+ "Topic :: System :: Monitoring",
20
+ "Typing :: Typed",
21
+ ]
22
+ dependencies = [
23
+ "pydantic>=2.10.0",
24
+ "httpx>=0.28.0",
25
+ ]
26
+
27
+ [project.urls]
28
+ Homepage = "https://rootmark.dev"
29
+ Documentation = "https://rootmark.dev/docs/"
30
+ Source = "https://github.com/IvanAdactic/rootmark"
31
+
32
+ [project.optional-dependencies]
33
+ # OpenAI integration (Group 4); tested with 1.x and 2.x
34
+ openai = ["openai>=1.40.0,<3"]
35
+ # LangChain callback handler (D4.7); BaseCallbackHandler is stable across 0.3.x and 1.x
36
+ langchain = ["langchain-core>=0.3,<2"]
37
+ # CrewAI event listeners (D5.6); BaseEventListener + crewai_event_bus (tested 0.86–1.x)
38
+ crewai = ["crewai>=0.86,<2"]
39
+ # Strands SDK / AWS Bedrock agent patch (D6.1)
40
+ strands = ["strands-agents>=1.26.0"]
41
+ # Boto3 / botocore interceptor for Bedrock zero-code-change path (D6.3)
42
+ boto3 = ["boto3>=1.35.0"]
43
+ dev = ["pytest>=8.0", "pytest-asyncio>=0.24", "openai>=1.40.0,<3", "langchain-core>=0.3,<2", "crewai>=0.86,<2"]
44
+
45
+ [build-system]
46
+ requires = ["hatchling"]
47
+ build-backend = "hatchling.build"
48
+
49
+ [tool.hatch.build.targets.wheel]
50
+ packages = ["src/rootmark"]
51
+
52
+ [tool.pytest.ini_options]
53
+ testpaths = ["tests"]
54
+ pythonpath = ["src"]
55
+ markers = [
56
+ "integration: needs docker-compose (API, worker, ClickHouse, Postgres)",
57
+ ]
58
+