probe-research 0.6.0__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 (66) hide show
  1. probe_research-0.6.0/.gitignore +7 -0
  2. probe_research-0.6.0/PKG-INFO +308 -0
  3. probe_research-0.6.0/README.md +280 -0
  4. probe_research-0.6.0/deploy/mcp/README.md +45 -0
  5. probe_research-0.6.0/fixtures/rl_offpolicy/README.md +45 -0
  6. probe_research-0.6.0/plugins/probe-research/README.md +46 -0
  7. probe_research-0.6.0/plugins/probe-research/skills/manage-research-asset/SKILL.md +16 -0
  8. probe_research-0.6.0/plugins/probe-research/skills/manage-research-asset/agents/openai.yaml +4 -0
  9. probe_research-0.6.0/plugins/probe-research/skills/publish-experiment/SKILL.md +18 -0
  10. probe_research-0.6.0/plugins/probe-research/skills/publish-experiment/agents/openai.yaml +4 -0
  11. probe_research-0.6.0/plugins/probe-research/skills/track-experiment/SKILL.md +16 -0
  12. probe_research-0.6.0/plugins/probe-research/skills/track-experiment/agents/openai.yaml +4 -0
  13. probe_research-0.6.0/pyproject.toml +72 -0
  14. probe_research-0.6.0/skills/manage-research-asset/SKILL.md +16 -0
  15. probe_research-0.6.0/skills/manage-research-asset/agents/openai.yaml +4 -0
  16. probe_research-0.6.0/skills/publish-experiment/SKILL.md +18 -0
  17. probe_research-0.6.0/skills/publish-experiment/agents/openai.yaml +4 -0
  18. probe_research-0.6.0/skills/track-experiment/SKILL.md +16 -0
  19. probe_research-0.6.0/skills/track-experiment/agents/openai.yaml +4 -0
  20. probe_research-0.6.0/src/probe/__init__.py +31 -0
  21. probe_research-0.6.0/src/probe/_generated/__init__.py +0 -0
  22. probe_research-0.6.0/src/probe/_generated/models.py +1189 -0
  23. probe_research-0.6.0/src/probe/cli/__init__.py +16 -0
  24. probe_research-0.6.0/src/probe/cli/main.py +1266 -0
  25. probe_research-0.6.0/src/probe/client.py +5 -0
  26. probe_research-0.6.0/src/probe/config.py +13 -0
  27. probe_research-0.6.0/src/probe/connectors/__init__.py +2 -0
  28. probe_research-0.6.0/src/probe/connectors/harbor.py +289 -0
  29. probe_research-0.6.0/src/probe/errors.py +3 -0
  30. probe_research-0.6.0/src/probe/mcp/__init__.py +6 -0
  31. probe_research-0.6.0/src/probe/mcp/contract.py +95 -0
  32. probe_research-0.6.0/src/probe/mcp/server.py +380 -0
  33. probe_research-0.6.0/src/probe/mcp/service.py +537 -0
  34. probe_research-0.6.0/src/probe/mcp/source.py +209 -0
  35. probe_research-0.6.0/src/probe/models.py +112 -0
  36. probe_research-0.6.0/src/probe/run.py +5 -0
  37. probe_research-0.6.0/src/probe/sdk/__init__.py +23 -0
  38. probe_research-0.6.0/src/probe/sdk/assets.py +169 -0
  39. probe_research-0.6.0/src/probe/sdk/client.py +727 -0
  40. probe_research-0.6.0/src/probe/sdk/config.py +118 -0
  41. probe_research-0.6.0/src/probe/sdk/defaults.py +98 -0
  42. probe_research-0.6.0/src/probe/sdk/device.py +164 -0
  43. probe_research-0.6.0/src/probe/sdk/errors.py +94 -0
  44. probe_research-0.6.0/src/probe/sdk/events.py +102 -0
  45. probe_research-0.6.0/src/probe/sdk/run.py +486 -0
  46. probe_research-0.6.0/src/probe/sdk/sessions.py +156 -0
  47. probe_research-0.6.0/src/probe/sdk/snapshot.py +145 -0
  48. probe_research-0.6.0/src/probe/sdk/spool.py +86 -0
  49. probe_research-0.6.0/src/probe/sdk/transport.py +209 -0
  50. probe_research-0.6.0/src/probe/snapshot.py +5 -0
  51. probe_research-0.6.0/src/probe/spool.py +5 -0
  52. probe_research-0.6.0/src/probe/transport.py +5 -0
  53. probe_research-0.6.0/tests/conftest.py +630 -0
  54. probe_research-0.6.0/tests/test_cli.py +123 -0
  55. probe_research-0.6.0/tests/test_device_login.py +94 -0
  56. probe_research-0.6.0/tests/test_harbor_connector.py +188 -0
  57. probe_research-0.6.0/tests/test_lifecycle.py +336 -0
  58. probe_research-0.6.0/tests/test_mcp.py +616 -0
  59. probe_research-0.6.0/tests/test_mcp_hosted.py +260 -0
  60. probe_research-0.6.0/tests/test_mcp_oauth_discovery.py +103 -0
  61. probe_research-0.6.0/tests/test_mcp_token.py +207 -0
  62. probe_research-0.6.0/tests/test_onboarding.py +203 -0
  63. probe_research-0.6.0/tests/test_parity.py +528 -0
  64. probe_research-0.6.0/tests/test_sdk.py +406 -0
  65. probe_research-0.6.0/tests/test_snapshot.py +73 -0
  66. probe_research-0.6.0/tests/test_surfaces.py +112 -0
@@ -0,0 +1,7 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.egg-info/
4
+ *.pyc
5
+ .pytest_cache/
6
+ dist/
7
+ build/
@@ -0,0 +1,308 @@
1
+ Metadata-Version: 2.4
2
+ Name: probe-research
3
+ Version: 0.6.0
4
+ Summary: CLI + SDK + read-only MCP client for Probe Research (experiment tracking).
5
+ Project-URL: Homepage, https://github.com/prbe-ai/research-os-agent
6
+ Project-URL: Repository, https://github.com/prbe-ai/research-os-agent
7
+ Author-email: Probe <team@prbe.ai>
8
+ License: MIT
9
+ Keywords: experiment-tracking,mcp,mlops,probe-research,reinforcement-learning
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Science/Research
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3 :: Only
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
17
+ Requires-Python: >=3.11
18
+ Requires-Dist: httpx>=0.27
19
+ Requires-Dist: mcp<2,>=1.23
20
+ Requires-Dist: pydantic>=2.9
21
+ Requires-Dist: typer<1.0,>=0.12
22
+ Provides-Extra: dev
23
+ Requires-Dist: datamodel-code-generator>=0.25; extra == 'dev'
24
+ Requires-Dist: pytest>=8; extra == 'dev'
25
+ Provides-Extra: mcp-http
26
+ Requires-Dist: uvicorn>=0.30; extra == 'mcp-http'
27
+ Description-Content-Type: text/markdown
28
+
29
+ # probe-research (`probe` SDK/CLI + `probe-research` plugin)
30
+
31
+ CLI + SDK client for **Probe Research**, Probe's experiment-tracking platform. It is a
32
+ thin client over the v3 ingestion contract (`CONTRACT.md` in the Probe Research backend).
33
+ Implemented experiment calls map onto real endpoints. Target asset-registry
34
+ methods are present as an explicit client contract but fail closed with a
35
+ capability error until the backend routes exist.
36
+
37
+ ## Two client surfaces
38
+
39
+ Probe Research exposes experiment tracking through two separate surfaces over the same backend, for two different workflows:
40
+
41
+ - **`probe` — SDK + CLI (non-agent).** A Python library (`import probe`) and the `probe` command-line tool for integrating with existing setups and manual experimentation. Drop it into a training script or pipeline to record runs, metrics, spans, and artifacts. No agent required.
42
+ - **`probe-research` — plugin: skills + MCP (agent-centric).** Installed into a coding agent (e.g. Claude Code). Its skills teach the agent the experiment workflow, its read-only MCP server lets the agent query experiment state, and writes flow through the `probe` CLI. This is the surface for agent-driven research loops such as Anthrogen.
43
+
44
+ Same backend, two entry points: humans-in-code reach for the SDK/CLI; agents-in-the-loop use the plugin.
45
+
46
+ ## Package boundaries
47
+
48
+ ```text
49
+ src/probe/
50
+ ├── sdk/ # typed client, uploads, local capture, session adapter ABI
51
+ ├── cli/ # `probe`: thin shell over the SDK
52
+ └── mcp/ # `probe-research-mcp`: strictly read-only tools and resources
53
+ skills/
54
+ ├── track-experiment/
55
+ ├── manage-research-asset/
56
+ └── publish-experiment/
57
+ ```
58
+
59
+ The SDK is the implementation. The CLI, MCP source adapter, future hooks,
60
+ Python experiments, and passive platform integrations all use it. CLI and SDK
61
+ therefore have capability parity; they differ only in ergonomics.
62
+
63
+ | Surface | SDK | CLI | Intended caller |
64
+ |---|---|---|---|
65
+ | Experiment upload | `Client.run`, `Run.log/span/log_artifact/snapshot/link/execute`, `Client.events`, `Client.promote` | `run`, `log`, `span`, `artifact`, `snapshot`, `link`, `exec`, `event`, `promote` | Researchers, agents, notebooks, training/platform code |
66
+ | Session adapter | `Client.sessions.attach/checkpoint/detach` | `probe hook session ...` | **Future deterministic hooks/broker only** |
67
+ | Asset read/selection | `Client.assets.resolve`, normally behind MCP | No normal read verb | Agent through read-only MCP |
68
+ | Asset effects | `Client.assets.materialize/fork/propose/promote` | `probe asset ...` | Agent/researcher after selecting an exact asset ref |
69
+ | Passive ingestion | `Client.ingest` | No convenience command yet | Install-once platform integration |
70
+ | Read plane | SDK reads used by `probe.mcp` | `get`/`bundle` diagnostics | MCP for agents; CLI for humans/scripts |
71
+
72
+ Session commands do not upload metrics or experiment outputs. They correlate a
73
+ coding-agent session with a run and checkpoint redacted transcript metadata.
74
+ Conversely, `event add` is normal experiment knowledge upload even when a hook
75
+ eventually calls it. No hooks are installed in this release.
76
+
77
+ ## Install
78
+
79
+ ```bash
80
+ pip install -e ".[dev]" # from this directory
81
+ ```
82
+
83
+ ## Auth
84
+
85
+ ```bash
86
+ probe login # browser device flow (RFC 8628 + PKCE) — the default; nothing to paste
87
+ ```
88
+
89
+ Air-gap paste path: `probe login --token probe_pat_xxxxxxxx` (verified via `GET /v1/me`);
90
+ `probe login --endpoint-only --base-url …` saves the endpoint without minting a token.
91
+ Both write `~/.config/probe/config.json`.
92
+ Or set env: `PROBE_BASE_URL`, `PROBE_TOKEN` (user token, `/v1`), `PROBE_INGEST_TOKEN`
93
+ (ingest token, `/ingest`), `PROBE_HMAC_SECRET` (optional body-signature secret).
94
+
95
+ You can also skip `probe login` entirely: the first `client.run()` /
96
+ `probe run start` with no token triggers the same browser approval inline (TTY only)
97
+ and persists the result. Disable with `PROBE_AUTO_LOGIN=0`; headless/CI keeps the
98
+ crisp `AuthError` and should set `PROBE_TOKEN`.
99
+
100
+ The MCP server prefers `PROBE_MCP_TOKEN`, which should be a separately minted
101
+ read-only token. It falls back to `PROBE_TOKEN` for local development, but exposes
102
+ no mutation tools.
103
+
104
+ On rented compute (RunPod) with no standing config, the `/track-experiment` skill seeds
105
+ `PROBE_TOKEN` at session start.
106
+
107
+ ## SDK (agent-driven / interactive)
108
+
109
+ ```python
110
+ import probe
111
+
112
+ client = probe.Client() # resolves creds from env / `probe login`
113
+
114
+ run = client.run(experiment="dockq-sweep", hypothesis="temp 0.7 wins", name="run-1",
115
+ project="folding", source="runpod", external_id="rp-9931")
116
+ # …or with zero identity args: `client.run()` defaults experiment to the git repo /
117
+ # script name, name to a timestamp (the server adds a petname short_id), and a NEW
118
+ # experiment gets a marked "[auto] …" hypothesis composed from context. Set the real
119
+ # one later: client.update_experiment(id, hypothesis="…") / probe experiment set.
120
+
121
+ run.snapshot() # non-disruptive git + deps + GPU capture
122
+ run.link(wandb_run_id="abc123", s3_prefix="s3://x/y")
123
+
124
+ for step in range(100):
125
+ run.log({"loss": ..., "dockq": ...}, step=step) # POST /v1/runs/{id}/metrics
126
+
127
+ sid = run.span("rollout", name="rollout-0", step_index=1) # trajectory span
128
+ run.log_artifact("final.sif", uri="r2://bucket/final.sif", kind="artifact")
129
+ run.finish() # flushes spool, sets status+ended_at
130
+ ```
131
+
132
+ Structured knowledge and local process capture use the same SDK:
133
+
134
+ ```python
135
+ run.execute(["python", "train.py", "--config", "dockq.yaml"])
136
+ client.events.add(run.id, "decision", "Use DockQ scorer v3", evidence_refs=["tool:91"])
137
+ report = client.check_run(run.id)
138
+ ```
139
+
140
+ Data writes are **fail-open** by default: on failure they spool to disk
141
+ (`~/.local/state/probe/spool`) and return, never blocking the training loop. `run.finish()`
142
+ (or `probe flush`) replays the spool. Pass `strict=True` to make a write raise.
143
+
144
+ ## SDK (install-once / passive push)
145
+
146
+ ```python
147
+ client.ingest(
148
+ experiment_slug="dockq", experiment_hypothesis="...",
149
+ run={"name": "r1", "source": "temporal", "external_id": "wf-1", "status": "running"},
150
+ metrics=[{"kind": "model", "key": "loss", "value": 0.5, "step_index": 1}],
151
+ batch_id="deadbeef", # idempotent redelivery
152
+ )
153
+ ```
154
+
155
+ One idempotent push (bearer ingest token + optional HMAC), keyed on
156
+ `(customer_id, source, external_id)`.
157
+
158
+ ## CLI (`probe`)
159
+
160
+ ```bash
161
+ RUN=$(probe run start --experiment dockq --hypothesis "temp 0.7 wins" --name run-1 \
162
+ --project folding --source runpod --external-id rp-9931)
163
+ probe snapshot $RUN
164
+ probe link $RUN --set wandb_run_id=abc --set gpu_job=rp-9931
165
+ probe log $RUN loss=0.42 dockq=0.71 --step 42
166
+ probe span add $RUN --type rollout --name rollout-0 --step 1
167
+ probe artifact add $RUN ./final.sif --kind artifact
168
+ probe event add $RUN --kind decision --statement "Use DockQ scorer v3" --evidence tool:91
169
+ probe exec $RUN -- python train.py --config dockq.yaml
170
+ probe run check $RUN
171
+ probe run end $RUN --status completed
172
+ probe bundle $RUN # read: run + series + artifacts
173
+ ```
174
+
175
+ The following commands are reserved for future hook configuration and are not
176
+ part of the normal researcher workflow:
177
+
178
+ ```bash
179
+ probe hook session attach RUN --session-id SESSION --transcript-path PATH --cwd DIR
180
+ probe hook session checkpoint RUN --session-id SESSION --transcript-path PATH --reason pre_compact
181
+ probe hook session detach RUN --session-id SESSION --reason session_end
182
+ ```
183
+
184
+ They currently encode session links in `run.metadata.agent.sessions[]` and
185
+ transcript checkpoints as redacted local-reference artifacts. Until managed
186
+ artifact upload exists, transcript portability remains explicitly false.
187
+
188
+ ## Read-only MCP server
189
+
190
+ Run the stdio server with `probe-research-mcp`. It exposes exactly six tools:
191
+
192
+ | Tool | Function |
193
+ |---|---|
194
+ | `research_context` | Project/session bootstrap, prior experiments, active runs, capability warnings |
195
+ | `research_search` | One-index exact+semantic backend search (`POST /v1/search`, corpora: assets/procedures → files, documents → github+files, transcripts unsupported); keyword fallback on pre-search backends |
196
+ | `research_get` | Progressive card, handoff, reproduction, lineage, metrics, and artifact views |
197
+ | `research_compare` | Server-side comparison of runs, experiments, and future asset versions |
198
+ | `research_resolve` | Compatible asset resolution; honest partial result on API v3 |
199
+ | `research_trace_file` | Producer-consumer and cleanup lineage; partial until trace indexing lands |
200
+
201
+ MCP reads through the Probe Research API—never directly from Postgres or R2. Its
202
+ logical sources are control identity/tenant scope, the structured experiment
203
+ store, the asset/manifest registry, the one-index search door (`POST /v1/search`:
204
+ exact SQL channel + the KB engine's semantic channel; search capabilities are
205
+ discovered against the live backend with one cached probe), and object-store
206
+ resource pointers returned by the API. W&B, RunPod, Kubernetes,
207
+ Git, and local transcript paths are not live MCP sources; adapters upload their
208
+ identifiers and evidence first.
209
+
210
+ ## Skills
211
+
212
+ - `experiment` mentally boxes result-producing work and uploads concise evidence.
213
+ - `manage-research-asset` resolves before create, reuses exact versions, forks
214
+ immutable bases, and proposes candidates without filename-based sprawl.
215
+ - `publish-experiment` requires explicit approval and refuses to imitate official
216
+ promotion when manifest/asset capabilities are unavailable.
217
+
218
+ Asset-reuse hooks are deliberately deferred. The track-experiment skill contains the
219
+ reuse-before-create rule; deterministic enforcement can be added later without
220
+ changing the SDK, CLI, MCP, or skill contracts.
221
+
222
+ ## What maps to what (v3 endpoints)
223
+
224
+ | Client call | Endpoint |
225
+ |---|---|
226
+ | `client.run()` / `run.child()` | `POST /v1/experiments`, `POST /v1/experiments/{id}/runs` |
227
+ | `run.log()` / `run.log_hw()` | `POST /v1/runs/{id}/metrics` |
228
+ | `run.span()` / `run.step()` | `POST /v1/runs/{id}/spans` \| `/steps` |
229
+ | `run.log_artifact()` | `POST /v1/runs/{id}/artifacts` |
230
+ | `run.link()` | `PATCH /v1/runs/{id}` (merges `metadata.foreign_keys`) |
231
+ | `run.finish()` | `PATCH /v1/runs/{id}` |
232
+ | `client.events.add()` | `POST /v1/runs/{id}/artifacts` (`kind=research_event`, v3 encoding) |
233
+ | `client.sessions.*` | `PATCH /v1/runs/{id}` + transcript artifact metadata (hook ABI) |
234
+ | `client.ingest()` | `POST /ingest/v1/runs` |
235
+ | `client.run_bundle()` / `run_lineage()` | `GET /v1/runs/{id}/bundle` \| `/lineage` |
236
+ | `client.search()` (used by `research_search`) | `POST /v1/search` (exact+semantic, sectioned) |
237
+
238
+ ## v0.4.0.0 ingestion fold-in (Phase 1)
239
+
240
+ Most earlier gaps are closed by Probe Research v0.4 (PR #13). Now wired:
241
+
242
+ - **Real metric dimensions.** `log_hw(..., device=3, host="n1")` sends `dimensions`
243
+ (fold #9); `log(..., dimensions={...})`. No more key-encoding.
244
+ - **Presign artifact upload.** `log_artifact(path=...)` runs presign → PUT to R2 →
245
+ confirm (fold #16), carrying `kind`/`meta` so byte uploads are labeled like
246
+ reference artifacts (Harbor-ownership Phase 0). Fails open to a reference on error.
247
+ - **Execution records.** `snapshot()` posts a content-addressed `execution-record`
248
+ (fold #7); `client.execution_record(...)`.
249
+ - **Asset registry.** `client.assets.register()` + `add_version()` + `resolve()`
250
+ (fold #5). The aspirational fork/propose/promote-candidate surface was dropped
251
+ (promotion tiers rejected upstream).
252
+ - **Experiment versions.** `client.experiment_version()` mints the immutable manifest
253
+ (fold #6). This replaces the removed run-level `promote`.
254
+ - **Lineage edges.** `client.add_edge()` / `run.edges()` (fold #2).
255
+ - **foreign_keys.** first-class on the ingest path (`run['foreign_keys']`, fold #8) and
256
+ surfaced on reads (`run.foreign_keys`, `run.short_id`).
257
+ - **Events read.** `client.events.list()` / `for_run()` (server-emitted lifecycle log).
258
+ Research notes moved to `client.notes.add()` (stored as `kind="note"` artifacts).
259
+
260
+ ### Remaining
261
+
262
+ - **MCP semantic/KB search** is now wired to `POST /v1/search` (workspaces+kb
263
+ fold-in) with an honest keyword fallback on older backends; transcript
264
+ evidence is not indexed yet. **Session hooks** remain later work.
265
+ - **Harbor-native ownership Phases 1–3** (trial capture connector, capture-at-source,
266
+ platform surface): see `docs/2026-07-15-harbor-native-ownership-plan.md`.
267
+
268
+ (Previously listed here and since shipped: `RunPatch` `foreign_keys`/`env_ref` parity,
269
+ asset `materialize`, upload `kind`/`meta`, and server-side artifact list filters
270
+ `?kind=&step_from=&step_to=`.)
271
+
272
+ ## Typed models (generated from the OpenAPI contract)
273
+
274
+ Request/response models are generated from the backend's OpenAPI schema, not
275
+ hand-written, so the client cannot silently drift from the contract. The write
276
+ paths (`log`/`span`/`log_artifact`/`ingest`/`assets`/`edges`/`execution-records`)
277
+ build their payloads through the generated models, so a renamed or removed field
278
+ fails client-side instead of as a server 422. `/ingest/v1/runs` is now declared in
279
+ the schema too (Probe Research PR #12), so the passive push is generated and validated
280
+ like every other path.
281
+
282
+ - `schema/openapi.json` - a snapshot of Probe Research's FastAPI schema.
283
+ - `src/probe/_generated/models.py` - generated, never hand-edited.
284
+ - `src/probe/models.py` - the stable import seam the SDK uses.
285
+
286
+ Refresh when the contract moves:
287
+
288
+ ```bash
289
+ make regen # dump-openapi (RESEARCH_OS=../../research-os) + gen-models
290
+ # or step by step:
291
+ RESEARCH_OS=/path/to/research-os python scripts/dump_openapi.py
292
+ python scripts/gen_models.py
293
+ ```
294
+
295
+ `RESEARCH_OS` points at a local checkout of the Probe Research backend source repo
296
+ (directory name `research-os`); it is only used to regenerate the schema snapshot.
297
+
298
+ ## CLI grammar note
299
+
300
+ The CLI is built on **typer**. Connection flags are global and go *before* the
301
+ command: `probe --token probe_pat_x log RUN loss=0.1`. `probe login` also accepts them
302
+ directly (`probe login --token ...`).
303
+
304
+ ## Tests
305
+
306
+ ```bash
307
+ pytest # 29 mocked/unit tests + a real-git snapshot test; no live server
308
+ ```
@@ -0,0 +1,280 @@
1
+ # probe-research (`probe` SDK/CLI + `probe-research` plugin)
2
+
3
+ CLI + SDK client for **Probe Research**, Probe's experiment-tracking platform. It is a
4
+ thin client over the v3 ingestion contract (`CONTRACT.md` in the Probe Research backend).
5
+ Implemented experiment calls map onto real endpoints. Target asset-registry
6
+ methods are present as an explicit client contract but fail closed with a
7
+ capability error until the backend routes exist.
8
+
9
+ ## Two client surfaces
10
+
11
+ Probe Research exposes experiment tracking through two separate surfaces over the same backend, for two different workflows:
12
+
13
+ - **`probe` — SDK + CLI (non-agent).** A Python library (`import probe`) and the `probe` command-line tool for integrating with existing setups and manual experimentation. Drop it into a training script or pipeline to record runs, metrics, spans, and artifacts. No agent required.
14
+ - **`probe-research` — plugin: skills + MCP (agent-centric).** Installed into a coding agent (e.g. Claude Code). Its skills teach the agent the experiment workflow, its read-only MCP server lets the agent query experiment state, and writes flow through the `probe` CLI. This is the surface for agent-driven research loops such as Anthrogen.
15
+
16
+ Same backend, two entry points: humans-in-code reach for the SDK/CLI; agents-in-the-loop use the plugin.
17
+
18
+ ## Package boundaries
19
+
20
+ ```text
21
+ src/probe/
22
+ ├── sdk/ # typed client, uploads, local capture, session adapter ABI
23
+ ├── cli/ # `probe`: thin shell over the SDK
24
+ └── mcp/ # `probe-research-mcp`: strictly read-only tools and resources
25
+ skills/
26
+ ├── track-experiment/
27
+ ├── manage-research-asset/
28
+ └── publish-experiment/
29
+ ```
30
+
31
+ The SDK is the implementation. The CLI, MCP source adapter, future hooks,
32
+ Python experiments, and passive platform integrations all use it. CLI and SDK
33
+ therefore have capability parity; they differ only in ergonomics.
34
+
35
+ | Surface | SDK | CLI | Intended caller |
36
+ |---|---|---|---|
37
+ | Experiment upload | `Client.run`, `Run.log/span/log_artifact/snapshot/link/execute`, `Client.events`, `Client.promote` | `run`, `log`, `span`, `artifact`, `snapshot`, `link`, `exec`, `event`, `promote` | Researchers, agents, notebooks, training/platform code |
38
+ | Session adapter | `Client.sessions.attach/checkpoint/detach` | `probe hook session ...` | **Future deterministic hooks/broker only** |
39
+ | Asset read/selection | `Client.assets.resolve`, normally behind MCP | No normal read verb | Agent through read-only MCP |
40
+ | Asset effects | `Client.assets.materialize/fork/propose/promote` | `probe asset ...` | Agent/researcher after selecting an exact asset ref |
41
+ | Passive ingestion | `Client.ingest` | No convenience command yet | Install-once platform integration |
42
+ | Read plane | SDK reads used by `probe.mcp` | `get`/`bundle` diagnostics | MCP for agents; CLI for humans/scripts |
43
+
44
+ Session commands do not upload metrics or experiment outputs. They correlate a
45
+ coding-agent session with a run and checkpoint redacted transcript metadata.
46
+ Conversely, `event add` is normal experiment knowledge upload even when a hook
47
+ eventually calls it. No hooks are installed in this release.
48
+
49
+ ## Install
50
+
51
+ ```bash
52
+ pip install -e ".[dev]" # from this directory
53
+ ```
54
+
55
+ ## Auth
56
+
57
+ ```bash
58
+ probe login # browser device flow (RFC 8628 + PKCE) — the default; nothing to paste
59
+ ```
60
+
61
+ Air-gap paste path: `probe login --token probe_pat_xxxxxxxx` (verified via `GET /v1/me`);
62
+ `probe login --endpoint-only --base-url …` saves the endpoint without minting a token.
63
+ Both write `~/.config/probe/config.json`.
64
+ Or set env: `PROBE_BASE_URL`, `PROBE_TOKEN` (user token, `/v1`), `PROBE_INGEST_TOKEN`
65
+ (ingest token, `/ingest`), `PROBE_HMAC_SECRET` (optional body-signature secret).
66
+
67
+ You can also skip `probe login` entirely: the first `client.run()` /
68
+ `probe run start` with no token triggers the same browser approval inline (TTY only)
69
+ and persists the result. Disable with `PROBE_AUTO_LOGIN=0`; headless/CI keeps the
70
+ crisp `AuthError` and should set `PROBE_TOKEN`.
71
+
72
+ The MCP server prefers `PROBE_MCP_TOKEN`, which should be a separately minted
73
+ read-only token. It falls back to `PROBE_TOKEN` for local development, but exposes
74
+ no mutation tools.
75
+
76
+ On rented compute (RunPod) with no standing config, the `/track-experiment` skill seeds
77
+ `PROBE_TOKEN` at session start.
78
+
79
+ ## SDK (agent-driven / interactive)
80
+
81
+ ```python
82
+ import probe
83
+
84
+ client = probe.Client() # resolves creds from env / `probe login`
85
+
86
+ run = client.run(experiment="dockq-sweep", hypothesis="temp 0.7 wins", name="run-1",
87
+ project="folding", source="runpod", external_id="rp-9931")
88
+ # …or with zero identity args: `client.run()` defaults experiment to the git repo /
89
+ # script name, name to a timestamp (the server adds a petname short_id), and a NEW
90
+ # experiment gets a marked "[auto] …" hypothesis composed from context. Set the real
91
+ # one later: client.update_experiment(id, hypothesis="…") / probe experiment set.
92
+
93
+ run.snapshot() # non-disruptive git + deps + GPU capture
94
+ run.link(wandb_run_id="abc123", s3_prefix="s3://x/y")
95
+
96
+ for step in range(100):
97
+ run.log({"loss": ..., "dockq": ...}, step=step) # POST /v1/runs/{id}/metrics
98
+
99
+ sid = run.span("rollout", name="rollout-0", step_index=1) # trajectory span
100
+ run.log_artifact("final.sif", uri="r2://bucket/final.sif", kind="artifact")
101
+ run.finish() # flushes spool, sets status+ended_at
102
+ ```
103
+
104
+ Structured knowledge and local process capture use the same SDK:
105
+
106
+ ```python
107
+ run.execute(["python", "train.py", "--config", "dockq.yaml"])
108
+ client.events.add(run.id, "decision", "Use DockQ scorer v3", evidence_refs=["tool:91"])
109
+ report = client.check_run(run.id)
110
+ ```
111
+
112
+ Data writes are **fail-open** by default: on failure they spool to disk
113
+ (`~/.local/state/probe/spool`) and return, never blocking the training loop. `run.finish()`
114
+ (or `probe flush`) replays the spool. Pass `strict=True` to make a write raise.
115
+
116
+ ## SDK (install-once / passive push)
117
+
118
+ ```python
119
+ client.ingest(
120
+ experiment_slug="dockq", experiment_hypothesis="...",
121
+ run={"name": "r1", "source": "temporal", "external_id": "wf-1", "status": "running"},
122
+ metrics=[{"kind": "model", "key": "loss", "value": 0.5, "step_index": 1}],
123
+ batch_id="deadbeef", # idempotent redelivery
124
+ )
125
+ ```
126
+
127
+ One idempotent push (bearer ingest token + optional HMAC), keyed on
128
+ `(customer_id, source, external_id)`.
129
+
130
+ ## CLI (`probe`)
131
+
132
+ ```bash
133
+ RUN=$(probe run start --experiment dockq --hypothesis "temp 0.7 wins" --name run-1 \
134
+ --project folding --source runpod --external-id rp-9931)
135
+ probe snapshot $RUN
136
+ probe link $RUN --set wandb_run_id=abc --set gpu_job=rp-9931
137
+ probe log $RUN loss=0.42 dockq=0.71 --step 42
138
+ probe span add $RUN --type rollout --name rollout-0 --step 1
139
+ probe artifact add $RUN ./final.sif --kind artifact
140
+ probe event add $RUN --kind decision --statement "Use DockQ scorer v3" --evidence tool:91
141
+ probe exec $RUN -- python train.py --config dockq.yaml
142
+ probe run check $RUN
143
+ probe run end $RUN --status completed
144
+ probe bundle $RUN # read: run + series + artifacts
145
+ ```
146
+
147
+ The following commands are reserved for future hook configuration and are not
148
+ part of the normal researcher workflow:
149
+
150
+ ```bash
151
+ probe hook session attach RUN --session-id SESSION --transcript-path PATH --cwd DIR
152
+ probe hook session checkpoint RUN --session-id SESSION --transcript-path PATH --reason pre_compact
153
+ probe hook session detach RUN --session-id SESSION --reason session_end
154
+ ```
155
+
156
+ They currently encode session links in `run.metadata.agent.sessions[]` and
157
+ transcript checkpoints as redacted local-reference artifacts. Until managed
158
+ artifact upload exists, transcript portability remains explicitly false.
159
+
160
+ ## Read-only MCP server
161
+
162
+ Run the stdio server with `probe-research-mcp`. It exposes exactly six tools:
163
+
164
+ | Tool | Function |
165
+ |---|---|
166
+ | `research_context` | Project/session bootstrap, prior experiments, active runs, capability warnings |
167
+ | `research_search` | One-index exact+semantic backend search (`POST /v1/search`, corpora: assets/procedures → files, documents → github+files, transcripts unsupported); keyword fallback on pre-search backends |
168
+ | `research_get` | Progressive card, handoff, reproduction, lineage, metrics, and artifact views |
169
+ | `research_compare` | Server-side comparison of runs, experiments, and future asset versions |
170
+ | `research_resolve` | Compatible asset resolution; honest partial result on API v3 |
171
+ | `research_trace_file` | Producer-consumer and cleanup lineage; partial until trace indexing lands |
172
+
173
+ MCP reads through the Probe Research API—never directly from Postgres or R2. Its
174
+ logical sources are control identity/tenant scope, the structured experiment
175
+ store, the asset/manifest registry, the one-index search door (`POST /v1/search`:
176
+ exact SQL channel + the KB engine's semantic channel; search capabilities are
177
+ discovered against the live backend with one cached probe), and object-store
178
+ resource pointers returned by the API. W&B, RunPod, Kubernetes,
179
+ Git, and local transcript paths are not live MCP sources; adapters upload their
180
+ identifiers and evidence first.
181
+
182
+ ## Skills
183
+
184
+ - `experiment` mentally boxes result-producing work and uploads concise evidence.
185
+ - `manage-research-asset` resolves before create, reuses exact versions, forks
186
+ immutable bases, and proposes candidates without filename-based sprawl.
187
+ - `publish-experiment` requires explicit approval and refuses to imitate official
188
+ promotion when manifest/asset capabilities are unavailable.
189
+
190
+ Asset-reuse hooks are deliberately deferred. The track-experiment skill contains the
191
+ reuse-before-create rule; deterministic enforcement can be added later without
192
+ changing the SDK, CLI, MCP, or skill contracts.
193
+
194
+ ## What maps to what (v3 endpoints)
195
+
196
+ | Client call | Endpoint |
197
+ |---|---|
198
+ | `client.run()` / `run.child()` | `POST /v1/experiments`, `POST /v1/experiments/{id}/runs` |
199
+ | `run.log()` / `run.log_hw()` | `POST /v1/runs/{id}/metrics` |
200
+ | `run.span()` / `run.step()` | `POST /v1/runs/{id}/spans` \| `/steps` |
201
+ | `run.log_artifact()` | `POST /v1/runs/{id}/artifacts` |
202
+ | `run.link()` | `PATCH /v1/runs/{id}` (merges `metadata.foreign_keys`) |
203
+ | `run.finish()` | `PATCH /v1/runs/{id}` |
204
+ | `client.events.add()` | `POST /v1/runs/{id}/artifacts` (`kind=research_event`, v3 encoding) |
205
+ | `client.sessions.*` | `PATCH /v1/runs/{id}` + transcript artifact metadata (hook ABI) |
206
+ | `client.ingest()` | `POST /ingest/v1/runs` |
207
+ | `client.run_bundle()` / `run_lineage()` | `GET /v1/runs/{id}/bundle` \| `/lineage` |
208
+ | `client.search()` (used by `research_search`) | `POST /v1/search` (exact+semantic, sectioned) |
209
+
210
+ ## v0.4.0.0 ingestion fold-in (Phase 1)
211
+
212
+ Most earlier gaps are closed by Probe Research v0.4 (PR #13). Now wired:
213
+
214
+ - **Real metric dimensions.** `log_hw(..., device=3, host="n1")` sends `dimensions`
215
+ (fold #9); `log(..., dimensions={...})`. No more key-encoding.
216
+ - **Presign artifact upload.** `log_artifact(path=...)` runs presign → PUT to R2 →
217
+ confirm (fold #16), carrying `kind`/`meta` so byte uploads are labeled like
218
+ reference artifacts (Harbor-ownership Phase 0). Fails open to a reference on error.
219
+ - **Execution records.** `snapshot()` posts a content-addressed `execution-record`
220
+ (fold #7); `client.execution_record(...)`.
221
+ - **Asset registry.** `client.assets.register()` + `add_version()` + `resolve()`
222
+ (fold #5). The aspirational fork/propose/promote-candidate surface was dropped
223
+ (promotion tiers rejected upstream).
224
+ - **Experiment versions.** `client.experiment_version()` mints the immutable manifest
225
+ (fold #6). This replaces the removed run-level `promote`.
226
+ - **Lineage edges.** `client.add_edge()` / `run.edges()` (fold #2).
227
+ - **foreign_keys.** first-class on the ingest path (`run['foreign_keys']`, fold #8) and
228
+ surfaced on reads (`run.foreign_keys`, `run.short_id`).
229
+ - **Events read.** `client.events.list()` / `for_run()` (server-emitted lifecycle log).
230
+ Research notes moved to `client.notes.add()` (stored as `kind="note"` artifacts).
231
+
232
+ ### Remaining
233
+
234
+ - **MCP semantic/KB search** is now wired to `POST /v1/search` (workspaces+kb
235
+ fold-in) with an honest keyword fallback on older backends; transcript
236
+ evidence is not indexed yet. **Session hooks** remain later work.
237
+ - **Harbor-native ownership Phases 1–3** (trial capture connector, capture-at-source,
238
+ platform surface): see `docs/2026-07-15-harbor-native-ownership-plan.md`.
239
+
240
+ (Previously listed here and since shipped: `RunPatch` `foreign_keys`/`env_ref` parity,
241
+ asset `materialize`, upload `kind`/`meta`, and server-side artifact list filters
242
+ `?kind=&step_from=&step_to=`.)
243
+
244
+ ## Typed models (generated from the OpenAPI contract)
245
+
246
+ Request/response models are generated from the backend's OpenAPI schema, not
247
+ hand-written, so the client cannot silently drift from the contract. The write
248
+ paths (`log`/`span`/`log_artifact`/`ingest`/`assets`/`edges`/`execution-records`)
249
+ build their payloads through the generated models, so a renamed or removed field
250
+ fails client-side instead of as a server 422. `/ingest/v1/runs` is now declared in
251
+ the schema too (Probe Research PR #12), so the passive push is generated and validated
252
+ like every other path.
253
+
254
+ - `schema/openapi.json` - a snapshot of Probe Research's FastAPI schema.
255
+ - `src/probe/_generated/models.py` - generated, never hand-edited.
256
+ - `src/probe/models.py` - the stable import seam the SDK uses.
257
+
258
+ Refresh when the contract moves:
259
+
260
+ ```bash
261
+ make regen # dump-openapi (RESEARCH_OS=../../research-os) + gen-models
262
+ # or step by step:
263
+ RESEARCH_OS=/path/to/research-os python scripts/dump_openapi.py
264
+ python scripts/gen_models.py
265
+ ```
266
+
267
+ `RESEARCH_OS` points at a local checkout of the Probe Research backend source repo
268
+ (directory name `research-os`); it is only used to regenerate the schema snapshot.
269
+
270
+ ## CLI grammar note
271
+
272
+ The CLI is built on **typer**. Connection flags are global and go *before* the
273
+ command: `probe --token probe_pat_x log RUN loss=0.1`. `probe login` also accepts them
274
+ directly (`probe login --token ...`).
275
+
276
+ ## Tests
277
+
278
+ ```bash
279
+ pytest # 29 mocked/unit tests + a real-git snapshot test; no live server
280
+ ```
@@ -0,0 +1,45 @@
1
+ # Hosting the read-only Research OS MCP
2
+
3
+ The hosted MCP is a **stateless** streamable-HTTP service. It holds no tenant token —
4
+ each request carries the caller's read-scoped `ros_pat` as `Authorization: Bearer …`,
5
+ and the server forwards that identity to the Research OS API (tenant isolation is the
6
+ API's existing RLS). So it scales horizontally and stores nothing.
7
+
8
+ Endpoint: `https://mcp.research.prbe.ai/mcp` · health: `/healthz`.
9
+
10
+ ## Run locally (dev / self-host)
11
+
12
+ ```bash
13
+ pip install -e ".[mcp-http]"
14
+ PROBE_BASE_URL=https://api.research.prbe.ai probe-research-mcp-http # serves :8080/mcp
15
+ curl -s localhost:8080/healthz # {"status":"ok"}
16
+ ```
17
+
18
+ Or stdio (single-tenant): `probe-research-mcp`. It takes the token from
19
+ `PROBE_MCP_TOKEN`, falling back to the `mcp_token` that `probe mcp token set` stores.
20
+ (The pre-rename `ROS_MCP_TOKEN` still works, deprecated, with a warning.)
21
+
22
+ ## Deploy to DOKS (Phase B — production; gated)
23
+
24
+ Prereqs already on the `research` cluster: ingress-nginx, cert-manager (`letsencrypt-prod`).
25
+
26
+ ```bash
27
+ # 1. build + push the image (GHCR)
28
+ docker build -f deploy/mcp/Dockerfile -t ghcr.io/prbe-ai/research-os-mcp:0.5.0 .
29
+ docker push ghcr.io/prbe-ai/research-os-mcp:0.5.0
30
+
31
+ # 2. DNS: point mcp.research.prbe.ai at the same ingress LB IP as api.research.prbe.ai
32
+
33
+ # 3. apply
34
+ kubectl apply -f deploy/mcp/k8s.yaml
35
+ kubectl -n research rollout status deploy/research-os-mcp
36
+
37
+ # 4. verify (cert may take a minute)
38
+ curl -s https://mcp.research.prbe.ai/healthz
39
+ ```
40
+
41
+ Notes:
42
+ - `k8s.yaml` points `PROBE_BASE_URL` at the in-cluster API service to avoid an LB hairpin;
43
+ swap to `https://api.research.prbe.ai` if you prefer the public endpoint.
44
+ - No secrets in the manifest — auth is per-request. Rate-limit at the ingress if needed.
45
+ - This is a **production change**; do it only with explicit go-ahead.