methodic-research 0.40.0__tar.gz → 0.41.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.
- {methodic_research-0.40.0/src/methodic_research.egg-info → methodic_research-0.41.0}/PKG-INFO +1 -1
- {methodic_research-0.40.0 → methodic_research-0.41.0}/pyproject.toml +1 -1
- {methodic_research-0.40.0 → methodic_research-0.41.0}/src/methodic/experiments.py +150 -0
- {methodic_research-0.40.0 → methodic_research-0.41.0/src/methodic_research.egg-info}/PKG-INFO +1 -1
- {methodic_research-0.40.0 → methodic_research-0.41.0}/tests/test_experiments.py +65 -0
- {methodic_research-0.40.0 → methodic_research-0.41.0}/LICENSE +0 -0
- {methodic_research-0.40.0 → methodic_research-0.41.0}/README.md +0 -0
- {methodic_research-0.40.0 → methodic_research-0.41.0}/setup.cfg +0 -0
- {methodic_research-0.40.0 → methodic_research-0.41.0}/src/methodic/__init__.py +0 -0
- {methodic_research-0.40.0 → methodic_research-0.41.0}/src/methodic/activity.py +0 -0
- {methodic_research-0.40.0 → methodic_research-0.41.0}/src/methodic/api_keys.py +0 -0
- {methodic_research-0.40.0 → methodic_research-0.41.0}/src/methodic/assets.py +0 -0
- {methodic_research-0.40.0 → methodic_research-0.41.0}/src/methodic/chronicle.py +0 -0
- {methodic_research-0.40.0 → methodic_research-0.41.0}/src/methodic/cli.py +0 -0
- {methodic_research-0.40.0 → methodic_research-0.41.0}/src/methodic/collections.py +0 -0
- {methodic_research-0.40.0 → methodic_research-0.41.0}/src/methodic/datasets.py +0 -0
- {methodic_research-0.40.0 → methodic_research-0.41.0}/src/methodic/errata.py +0 -0
- {methodic_research-0.40.0 → methodic_research-0.41.0}/src/methodic/errors.py +0 -0
- {methodic_research-0.40.0 → methodic_research-0.41.0}/src/methodic/feed.py +0 -0
- {methodic_research-0.40.0 → methodic_research-0.41.0}/src/methodic/feedback.py +0 -0
- {methodic_research-0.40.0 → methodic_research-0.41.0}/src/methodic/imports.py +0 -0
- {methodic_research-0.40.0 → methodic_research-0.41.0}/src/methodic/me.py +0 -0
- {methodic_research-0.40.0 → methodic_research-0.41.0}/src/methodic/pending.py +0 -0
- {methodic_research-0.40.0 → methodic_research-0.41.0}/src/methodic/publications.py +0 -0
- {methodic_research-0.40.0 → methodic_research-0.41.0}/src/methodic/reports.py +0 -0
- {methodic_research-0.40.0 → methodic_research-0.41.0}/src/methodic/research_prompts.py +0 -0
- {methodic_research-0.40.0 → methodic_research-0.41.0}/src/methodic/runs.py +0 -0
- {methodic_research-0.40.0 → methodic_research-0.41.0}/src/methodic/search.py +0 -0
- {methodic_research-0.40.0 → methodic_research-0.41.0}/src/methodic/tags.py +0 -0
- {methodic_research-0.40.0 → methodic_research-0.41.0}/src/methodic/transport.py +0 -0
- {methodic_research-0.40.0 → methodic_research-0.41.0}/src/methodic/types.py +0 -0
- {methodic_research-0.40.0 → methodic_research-0.41.0}/src/methodic/upload_tracker.py +0 -0
- {methodic_research-0.40.0 → methodic_research-0.41.0}/src/methodic/variations.py +0 -0
- {methodic_research-0.40.0 → methodic_research-0.41.0}/src/methodic_research.egg-info/SOURCES.txt +0 -0
- {methodic_research-0.40.0 → methodic_research-0.41.0}/src/methodic_research.egg-info/dependency_links.txt +0 -0
- {methodic_research-0.40.0 → methodic_research-0.41.0}/src/methodic_research.egg-info/entry_points.txt +0 -0
- {methodic_research-0.40.0 → methodic_research-0.41.0}/src/methodic_research.egg-info/requires.txt +0 -0
- {methodic_research-0.40.0 → methodic_research-0.41.0}/src/methodic_research.egg-info/top_level.txt +0 -0
- {methodic_research-0.40.0 → methodic_research-0.41.0}/tests/test_activity.py +0 -0
- {methodic_research-0.40.0 → methodic_research-0.41.0}/tests/test_api_keys.py +0 -0
- {methodic_research-0.40.0 → methodic_research-0.41.0}/tests/test_assets.py +0 -0
- {methodic_research-0.40.0 → methodic_research-0.41.0}/tests/test_cli.py +0 -0
- {methodic_research-0.40.0 → methodic_research-0.41.0}/tests/test_client.py +0 -0
- {methodic_research-0.40.0 → methodic_research-0.41.0}/tests/test_collections.py +0 -0
- {methodic_research-0.40.0 → methodic_research-0.41.0}/tests/test_config.py +0 -0
- {methodic_research-0.40.0 → methodic_research-0.41.0}/tests/test_datasets.py +0 -0
- {methodic_research-0.40.0 → methodic_research-0.41.0}/tests/test_distill.py +0 -0
- {methodic_research-0.40.0 → methodic_research-0.41.0}/tests/test_feed.py +0 -0
- {methodic_research-0.40.0 → methodic_research-0.41.0}/tests/test_feedback.py +0 -0
- {methodic_research-0.40.0 → methodic_research-0.41.0}/tests/test_imports.py +0 -0
- {methodic_research-0.40.0 → methodic_research-0.41.0}/tests/test_pending.py +0 -0
- {methodic_research-0.40.0 → methodic_research-0.41.0}/tests/test_pending_reasons.py +0 -0
- {methodic_research-0.40.0 → methodic_research-0.41.0}/tests/test_publications.py +0 -0
- {methodic_research-0.40.0 → methodic_research-0.41.0}/tests/test_reports.py +0 -0
- {methodic_research-0.40.0 → methodic_research-0.41.0}/tests/test_research_prompts.py +0 -0
- {methodic_research-0.40.0 → methodic_research-0.41.0}/tests/test_runs.py +0 -0
- {methodic_research-0.40.0 → methodic_research-0.41.0}/tests/test_search.py +0 -0
- {methodic_research-0.40.0 → methodic_research-0.41.0}/tests/test_upload_tracker.py +0 -0
- {methodic_research-0.40.0 → methodic_research-0.41.0}/tests/test_variations.py +0 -0
|
@@ -8,7 +8,7 @@ name = "methodic-research"
|
|
|
8
8
|
# methodic-lib-publish.yml overwrites this from versions.toml at build
|
|
9
9
|
# time, so the published artifact has a single source of truth. Kept
|
|
10
10
|
# here for local `pip install -e conductor/`.
|
|
11
|
-
version = "0.
|
|
11
|
+
version = "0.41.0"
|
|
12
12
|
description = "Python client for the Chronicle experiment platform"
|
|
13
13
|
requires-python = ">=3.11"
|
|
14
14
|
license = "Apache-2.0"
|
|
@@ -268,6 +268,146 @@ class ExperimentsAPI:
|
|
|
268
268
|
payload["source_asset_id"] = source_asset_id
|
|
269
269
|
return self._t.post(f"/experiments/{experiment_id}/findings", json=payload)
|
|
270
270
|
|
|
271
|
+
def record_lesson(
|
|
272
|
+
self,
|
|
273
|
+
experiment_id: str,
|
|
274
|
+
*,
|
|
275
|
+
title: str,
|
|
276
|
+
body_md: str,
|
|
277
|
+
origin: str,
|
|
278
|
+
category: str | None = None,
|
|
279
|
+
variation: int | None = None,
|
|
280
|
+
evidence: dict[str, Any] | None = None,
|
|
281
|
+
) -> dict[str, Any]:
|
|
282
|
+
"""Record a research lesson — a durable corrected assumption — onto
|
|
283
|
+
the experiment (designs/research-lessons.md).
|
|
284
|
+
|
|
285
|
+
Record one when you have repeatedly made the same bad assumption,
|
|
286
|
+
when the researcher explicitly or implicitly corrects a wrong
|
|
287
|
+
premise, or on request. List existing lessons first and refine
|
|
288
|
+
(:meth:`update_lesson`) instead of duplicating. Lessons surface on
|
|
289
|
+
the experiment's Lessons tab, are injected into every future
|
|
290
|
+
agent's context (this experiment + descendants), and emit a
|
|
291
|
+
``lesson.recorded`` feed event. Requires ``Write`` on the
|
|
292
|
+
experiment. (MCP-native agents have the same surface as the
|
|
293
|
+
``chronicle.record_lesson`` tool.)
|
|
294
|
+
|
|
295
|
+
Args:
|
|
296
|
+
experiment_id: Target experiment.
|
|
297
|
+
title: One line, imperative where possible.
|
|
298
|
+
body_md: The lesson (Markdown, self-contained): wrong
|
|
299
|
+
assumption, correction, evidence, what to do instead.
|
|
300
|
+
origin: ``"repeated_error"`` | ``"researcher_correction"`` |
|
|
301
|
+
``"self_identified"`` | ``"manual"``.
|
|
302
|
+
category: ``"assumption"`` (default) | ``"methodology"`` |
|
|
303
|
+
``"environment"`` | ``"data"`` | ``"other"``.
|
|
304
|
+
variation: The variation the lesson arose from, when
|
|
305
|
+
variation-specific.
|
|
306
|
+
evidence: Pointers only (session shard / run / report ids) —
|
|
307
|
+
never content.
|
|
308
|
+
|
|
309
|
+
Returns the recorded lesson.
|
|
310
|
+
"""
|
|
311
|
+
payload: dict[str, Any] = {
|
|
312
|
+
"title": title,
|
|
313
|
+
"body_md": body_md,
|
|
314
|
+
"origin": origin,
|
|
315
|
+
}
|
|
316
|
+
if category is not None:
|
|
317
|
+
payload["category"] = category
|
|
318
|
+
if variation is not None:
|
|
319
|
+
payload["variation"] = variation
|
|
320
|
+
if evidence is not None:
|
|
321
|
+
payload["evidence"] = evidence
|
|
322
|
+
return self._t.post(f"/experiments/{experiment_id}/lessons", json=payload)
|
|
323
|
+
|
|
324
|
+
def list_lessons(
|
|
325
|
+
self,
|
|
326
|
+
experiment_id: str,
|
|
327
|
+
*,
|
|
328
|
+
status: str | None = None,
|
|
329
|
+
category: str | None = None,
|
|
330
|
+
include_ancestors: bool = True,
|
|
331
|
+
) -> list[dict[str, Any]]:
|
|
332
|
+
"""List the experiment's research lessons — the corrected
|
|
333
|
+
assumptions every conclusion and proposal must be checked against.
|
|
334
|
+
|
|
335
|
+
Defaults to active lessons **including** those inherited from
|
|
336
|
+
ancestor experiments (each stamped ``inherited_from``). Consult
|
|
337
|
+
this before writing conclusions or proposing variations.
|
|
338
|
+
Requires ``Read`` on the experiment.
|
|
339
|
+
|
|
340
|
+
Args:
|
|
341
|
+
experiment_id: Target experiment.
|
|
342
|
+
status: ``"active"`` (default) | ``"retired"`` | ``"all"`` —
|
|
343
|
+
own-lesson filter; inherited lessons are always
|
|
344
|
+
active-only.
|
|
345
|
+
category: Optional category filter.
|
|
346
|
+
include_ancestors: Include ancestor experiments' active
|
|
347
|
+
lessons (default True).
|
|
348
|
+
|
|
349
|
+
Returns the lesson list.
|
|
350
|
+
"""
|
|
351
|
+
params: dict[str, Any] = {}
|
|
352
|
+
if status is not None:
|
|
353
|
+
params["status"] = status
|
|
354
|
+
if category is not None:
|
|
355
|
+
params["category"] = category
|
|
356
|
+
if include_ancestors:
|
|
357
|
+
params["include_ancestors"] = "true"
|
|
358
|
+
resp = self._t.get(
|
|
359
|
+
f"/experiments/{experiment_id}/lessons", params=params or None
|
|
360
|
+
)
|
|
361
|
+
return resp.get("lessons", [])
|
|
362
|
+
|
|
363
|
+
def update_lesson(
|
|
364
|
+
self,
|
|
365
|
+
experiment_id: str,
|
|
366
|
+
lesson_id: str,
|
|
367
|
+
*,
|
|
368
|
+
title: str | None = None,
|
|
369
|
+
body_md: str | None = None,
|
|
370
|
+
category: str | None = None,
|
|
371
|
+
variation: int | None = None,
|
|
372
|
+
evidence: dict[str, Any] | None = None,
|
|
373
|
+
) -> dict[str, Any]:
|
|
374
|
+
"""Refine an existing lesson in place (the dedup contract) — absent
|
|
375
|
+
fields are unchanged; retired lessons are immutable (409).
|
|
376
|
+
Requires ``Write`` on the experiment. Returns the updated lesson."""
|
|
377
|
+
payload: dict[str, Any] = {}
|
|
378
|
+
if title is not None:
|
|
379
|
+
payload["title"] = title
|
|
380
|
+
if body_md is not None:
|
|
381
|
+
payload["body_md"] = body_md
|
|
382
|
+
if category is not None:
|
|
383
|
+
payload["category"] = category
|
|
384
|
+
if variation is not None:
|
|
385
|
+
payload["variation"] = variation
|
|
386
|
+
if evidence is not None:
|
|
387
|
+
payload["evidence"] = evidence
|
|
388
|
+
return self._t.put(
|
|
389
|
+
f"/experiments/{experiment_id}/lessons/{lesson_id}", json=payload
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
def retire_lesson(
|
|
393
|
+
self,
|
|
394
|
+
experiment_id: str,
|
|
395
|
+
lesson_id: str,
|
|
396
|
+
*,
|
|
397
|
+
reason: str,
|
|
398
|
+
superseded_by: str | None = None,
|
|
399
|
+
) -> dict[str, Any]:
|
|
400
|
+
"""Retire a lesson that turned out wrong or obsolete (``active`` →
|
|
401
|
+
``retired``; retire-don't-delete). Only lessons owned by this
|
|
402
|
+
experiment — inherited ones are retired at their home experiment.
|
|
403
|
+
Requires ``Write`` on the experiment. Returns the retired lesson."""
|
|
404
|
+
payload: dict[str, Any] = {"reason": reason}
|
|
405
|
+
if superseded_by is not None:
|
|
406
|
+
payload["superseded_by"] = superseded_by
|
|
407
|
+
return self._t.put(
|
|
408
|
+
f"/experiments/{experiment_id}/lessons/{lesson_id}/retire", json=payload
|
|
409
|
+
)
|
|
410
|
+
|
|
271
411
|
def distill(
|
|
272
412
|
self,
|
|
273
413
|
experiment_id: str,
|
|
@@ -674,6 +814,16 @@ class Experiment:
|
|
|
674
814
|
self._detail = None
|
|
675
815
|
return summary_doc
|
|
676
816
|
|
|
817
|
+
def record_lesson(self, **kwargs: Any) -> dict[str, Any]:
|
|
818
|
+
"""Record a research lesson on this experiment. Proxies to
|
|
819
|
+
:meth:`ExperimentsAPI.record_lesson`."""
|
|
820
|
+
return self._chronicle.experiments.record_lesson(self.id, **kwargs)
|
|
821
|
+
|
|
822
|
+
def list_lessons(self, **kwargs: Any) -> list[dict[str, Any]]:
|
|
823
|
+
"""List this experiment's research lessons (own + inherited by
|
|
824
|
+
default). Proxies to :meth:`ExperimentsAPI.list_lessons`."""
|
|
825
|
+
return self._chronicle.experiments.list_lessons(self.id, **kwargs)
|
|
826
|
+
|
|
677
827
|
def distill(
|
|
678
828
|
self,
|
|
679
829
|
*,
|
|
@@ -937,3 +937,68 @@ def test_create_passes_title(requests_mock):
|
|
|
937
937
|
body = requests_mock.last_request.json()
|
|
938
938
|
assert body["title"] == "Display title"
|
|
939
939
|
assert body["hypothesis_summary"] == "h"
|
|
940
|
+
|
|
941
|
+
|
|
942
|
+
# --- Research lessons (designs/research-lessons.md) ---
|
|
943
|
+
|
|
944
|
+
def test_record_lesson_posts_body(requests_mock):
|
|
945
|
+
requests_mock.post(
|
|
946
|
+
f"{BASE}/v1/experiments/{EXP}/lessons",
|
|
947
|
+
json={"id": "les-1", "title": "check the mask", "status": "active"},
|
|
948
|
+
)
|
|
949
|
+
|
|
950
|
+
lesson = _chronicle().experiments.record_lesson(
|
|
951
|
+
EXP,
|
|
952
|
+
title="check the mask",
|
|
953
|
+
body_md="The boundary mask is inverted in this dataset.",
|
|
954
|
+
origin="researcher_correction",
|
|
955
|
+
category="data",
|
|
956
|
+
variation=4,
|
|
957
|
+
evidence={"run": 3},
|
|
958
|
+
)
|
|
959
|
+
|
|
960
|
+
assert lesson["id"] == "les-1"
|
|
961
|
+
sent = requests_mock.last_request.json()
|
|
962
|
+
assert sent == {
|
|
963
|
+
"title": "check the mask",
|
|
964
|
+
"body_md": "The boundary mask is inverted in this dataset.",
|
|
965
|
+
"origin": "researcher_correction",
|
|
966
|
+
"category": "data",
|
|
967
|
+
"variation": 4,
|
|
968
|
+
"evidence": {"run": 3},
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
|
|
972
|
+
def test_list_lessons_defaults_include_ancestors(requests_mock):
|
|
973
|
+
requests_mock.get(
|
|
974
|
+
f"{BASE}/v1/experiments/{EXP}/lessons",
|
|
975
|
+
json={"lessons": [{"id": "les-1"}, {"id": "les-2", "inherited_from": "parent-1"}]},
|
|
976
|
+
)
|
|
977
|
+
|
|
978
|
+
lessons = _chronicle().experiments.list_lessons(EXP)
|
|
979
|
+
|
|
980
|
+
assert [l["id"] for l in lessons] == ["les-1", "les-2"]
|
|
981
|
+
assert requests_mock.last_request.qs.get("include_ancestors") == ["true"]
|
|
982
|
+
|
|
983
|
+
|
|
984
|
+
def test_update_and_retire_lesson_put_bodies(requests_mock):
|
|
985
|
+
requests_mock.put(
|
|
986
|
+
f"{BASE}/v1/experiments/{EXP}/lessons/les-1",
|
|
987
|
+
json={"id": "les-1", "body_md": "sharper"},
|
|
988
|
+
)
|
|
989
|
+
updated = _chronicle().experiments.update_lesson(EXP, "les-1", body_md="sharper")
|
|
990
|
+
assert updated["body_md"] == "sharper"
|
|
991
|
+
assert requests_mock.last_request.json() == {"body_md": "sharper"}
|
|
992
|
+
|
|
993
|
+
requests_mock.put(
|
|
994
|
+
f"{BASE}/v1/experiments/{EXP}/lessons/les-1/retire",
|
|
995
|
+
json={"id": "les-1", "status": "retired"},
|
|
996
|
+
)
|
|
997
|
+
retired = _chronicle().experiments.retire_lesson(
|
|
998
|
+
EXP, "les-1", reason="fixed upstream", superseded_by="les-2"
|
|
999
|
+
)
|
|
1000
|
+
assert retired["status"] == "retired"
|
|
1001
|
+
assert requests_mock.last_request.json() == {
|
|
1002
|
+
"reason": "fixed upstream",
|
|
1003
|
+
"superseded_by": "les-2",
|
|
1004
|
+
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{methodic_research-0.40.0 → methodic_research-0.41.0}/src/methodic_research.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{methodic_research-0.40.0 → methodic_research-0.41.0}/src/methodic_research.egg-info/requires.txt
RENAMED
|
File without changes
|
{methodic_research-0.40.0 → methodic_research-0.41.0}/src/methodic_research.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|