Client reference¶
Generated from the SDK docstrings. Every operation lives on a namespace off
Chronicle; the resource handles (Experiment, Variation, Run, Report)
are sugar for chained drill-downs. For task-oriented walkthroughs, start with the
Guide.
Top-level¶
methodic.Chronicle ¶
Chronicle(
server_url: str,
api_key: str,
timeout: int = 30,
max_upload_workers: int = 2,
organization_id: str | None = None,
organization_slug: str | None = None,
)
Client for the Chronicle REST API.
Construct once with the server URL + API key, then call into namespaces:
chronicle = Chronicle(server_url="https://api.methodiclabs.ai", api_key="sk_...")
Or resolve credentials from the environment / a config file (see
from_env and from_file) — the form the Chronicle skills use:
chronicle = Chronicle.from_env() # CHRONICLE_SERVER_URL + CHRONICLE_API_KEY
Then call into namespaces:
# Researcher
exp = chronicle.experiments.create(hypothesis_summary="...", config_yaml="...")
exp.commit().variations.create(config_yaml="...")
# Worker
run = chronicle.run(experiment_id, variation, run_idx)
run.start().heartbeat()
run.upload_asset(asset_type="research_report", content={"summary": "..."})
run.succeed()
Use as a context manager to guarantee the executor and HTTP session are closed.
Construct via :meth:from_env (reads CHRONICLE_SERVER_URL +
CHRONICLE_API_KEY) or directly with server_url + api_key.
Organization scope resolves per call, with an optional client default:
pass organization_id on the call itself (e.g.
experiments.create(..., organization_id=...)), or set a default once —
organization_id: in ~/.methodic/config.yaml or
$CHRONICLE_ORGANIZATION_ID — and omit it. With a default configured,
pass methodic.PERSONAL to force a personal-scope call. There is no
per-request header; resolution happens in the SDK call.
organization_id
property
¶
The default organization for calls that take an organization_id
and were not given one (None = personal scope).
When configured by slug (organization_slug / the
CHRONICLE_ORGANIZATION_SLUG env / the organization_slug: config
key), the slug is resolved to its principal id via /v1/me/scopes on
first read and cached for the client's lifetime; a slug matching no
organization you belong to raises ChronicleConfigError.
experiment ¶
Get a handle for an existing experiment by id (lazy — no fetch until accessed).
from_env
classmethod
¶
Construct a client from the ambient environment.
Resolution order, highest precedence first:
- explicit keyword arguments —
server_url,api_key,timeout,max_upload_workers,organization_id - environment variables —
CHRONICLE_SERVER_URL,CHRONICLE_API_KEY,CHRONICLE_TIMEOUT,CHRONICLE_MAX_UPLOAD_WORKERS,CHRONICLE_ORGANIZATION_ID - YAML files under
~/.methodic—config.yaml(non-secret settings) thencredentials.yaml(the API key); or a single file pointed to byCHRONICLE_CONFIG - built-in defaults (
server_url→https://api.methodiclabs.ai)
api_key has no default; raises ChronicleConfigError if it cannot
be resolved from any source, or if a setting is malformed.
This is the entry point the Chronicle skills call, so setting either the environment variables or the config file is enough to run them.
from_file
classmethod
¶
from_file(
config: str | Path | None = None,
credentials: str | Path | None = None,
**overrides: Any,
) -> Chronicle
Construct a client from explicit YAML files.
Mirrors the ~/.methodic split so the same files work with either
entry point: a non-secret config file and/or a credentials file
holding the API key. Each is a flat mapping::
# config.yaml
server_url: https://api.methodiclabs.ai
timeout: 30 # optional
# credentials.yaml
api_key: sk_user_...
Either may carry any subset (a single combined file can be passed as
config); credentials wins over config on overlap, and
explicit keyword arguments win over both. Unlike from_env, this does
not consult environment variables. Raises ChronicleConfigError if a
named file is missing or malformed, or if no api_key is present.
run ¶
Construct a Run resource handle bound to one (experiment, variation, run).
variation ¶
Get a handle for an existing variation by (experiment_id, variation).
Resource handles¶
methodic.Experiment ¶
Experiment(
chronicle: Chronicle,
experiment_id: str,
*,
_detail: ExperimentDetail | None = None,
_create_response: CreateExperimentResponse
| None = None,
)
Handle for one experiment.
Mutators (commit, conclude, retract) return self so callers can
chain (exp.commit().variations.create(...)). Cached detail is dropped
after each mutation; the next attribute access re-fetches transparently.
commit ¶
Commit (lock the spec). Pass tentative_links (parent id →
"promote"/"drop") to resolve any tentative parent links, which
otherwise block commit. See :meth:ExperimentsAPI.commit.
delete ¶
Hard-delete this open experiment (see
:meth:ExperimentsAPI.delete). The handle is dead after this
returns — the experiment row and its cascade are gone.
distill ¶
distill(
*,
scope: str,
variation_id: int | None = None,
corpus_filter: dict[str, Any] | None = None,
write_research_report: bool = True,
reason: str | None = None,
) -> dict[str, Any]
Trigger a distillation agent for this experiment (M9 §17).
fork ¶
fork(
*,
hypothesis_summary: str,
rationale: str | None = None,
config_yaml: str | None = None,
slug: str | None = None,
allow_retracted_parent: bool = False,
) -> "Experiment"
Fork this experiment. Proxies to ExperimentsAPI.fork.
get_agent_config ¶
Read this experiment's agent_config block (M11).
git_status ¶
Lightweight current git-integration state for this experiment.
mint_git_token ¶
Mint a 1-hour install token scoped to this experiment's repo.
move ¶
move(
*,
organization_id: str,
team_id: str | None = None,
visibility: str | None = None,
) -> Experiment
Transfer this experiment into an org (see
:meth:ExperimentsAPI.move). Drops cached detail and returns
self so calls can chain.
promote_lineage ¶
Promote a tentative parent link to real lineage (parent must have
committed). See :meth:ExperimentsAPI.promote_lineage.
record_finding ¶
record_finding(
*,
status: str,
summary: str,
evidence_variation: int,
evidence_run: int | None = None,
source_asset_id: str | None = None,
) -> dict[str, Any]
Record a what's-working / what's-not finding onto this experiment's
running summary. Proxies to :meth:ExperimentsAPI.record_finding;
invalidates the cached detail so running_summary re-fetches.
set_agent_config ¶
Replace this experiment's agent_config block.
set_continuous_exploration ¶
set_continuous_exploration(
*,
enabled: bool,
trigger_scope: str = "variation",
cooldown_minutes: int = 0,
) -> dict[str, Any]
Configure the M11 continuous-exploration loop on this
experiment. Convenience wrapper that read-modify-writes
agent_config.continuous_exploration.
set_report_settings ¶
Replace experiment.report_settings. Frozen at commit (server
returns 409 once committed). settings shape matches the
ReportSettings server type:
{
"hypothesis": {"mode": "freeform", "freeform_prompt": "..."},
"takeaways": {"mode": "template", "template_asset_id": "...", "per_variation": true},
"research": {...}
}
Returns self for chaining. Drops cached _detail so the next
access re-fetches the updated row.
tentative_links ¶
This experiment's tentative parent lineage links (empty for the
common case). See :meth:ExperimentsAPI.tentative_links.
wait_for_repo ¶
Poll until this experiment's repo is ready (or failed/timeout).
methodic.Variation ¶
Variation(
chronicle: Chronicle,
experiment_id: str,
variation: int,
*,
_data: Variation | None = None,
)
Handle for one variation. Holds (experiment_id, variation) and lazy-loaded data.
data
property
¶
Server-side variation record. Auto-fetched on first access; refetched after mutations.
list_outputs ¶
List this variation's output assets across all runs (raw dicts), newest-first — produced checkpoints, snapshots, and reports.
set_git_ref ¶
Bind a branch to this (open) variation. Returns self for chaining.
unlink_input ¶
Unlink an input asset from this (open) variation. Returns self for chaining.
update ¶
update(
*,
hypothesis: str | None = None,
expected_outcome: str | None = None,
description: str | None = None,
name: str | None = None,
) -> Variation
Edit this (open) variation's mutable metadata. Returns self for chaining.
methodic.Run ¶
Handle for a specific (experiment_id, variation, run) run.
Mutators return self so worker code can chain (run.start().heartbeat()).
Asset-upload helpers auto-populate output_of from the bound triple.
create_asset_presigned ¶
create_asset_presigned(
asset_type: str,
components: list[str],
name: str | None = None,
content_type: str = "application/octet-stream",
) -> AssetUploadInfo
Register a new asset for component upload via presigned URLs.
latest_output ¶
latest_output(
asset_type: str | None = None,
*,
across_runs: bool = True,
ready_only: bool = True,
) -> dict[str, Any] | None
Return the most recent matching output asset, or None — the
resume-discovery helper.
Filters by asset_type (e.g. "checkpoint") and, by default, to
state == "ready" (finalized + immutable). Searches across all runs of
the variation by default. Pair with :meth:download_asset::
ckpt = run.latest_output("checkpoint")
if ckpt:
run.download_asset(ckpt["id"], Path("./resume"))
list_outputs ¶
List this run's output assets (newest-first). With
across_runs=True, list outputs across all runs of the variation
— the scope to use when resuming, since the checkpoint to resume from
was produced by an earlier run.
register_and_upload_async ¶
register_and_upload_async(
local_dir: Path,
asset_type: str,
upload_tracker: UploadTracker,
content_type: str = "application/octet-stream",
) -> str
Register every file in a directory, then upload + finalize on a background thread.
upload_asset ¶
upload_asset(
asset_type: str,
content: Any,
name: str | None = None,
content_type: str = "application/json",
asset_config: dict[str, Any] | None = None,
) -> dict[str, Any]
Upload a small inline asset (auto-finalized). Linked to this run as output.
upload_directory_async ¶
upload_directory_async(
local_dir: Path,
asset_type: str,
content_type: str = "application/octet-stream",
upload_tracker: UploadTracker | None = None,
) -> None
Upload every file in a directory on a background thread.
With upload_tracker, uses the register-then-upload flow for crash
recovery. Without, a thinner path that uploads and finalizes inline.
methodic.Report ¶
Report(
chronicle: Chronicle,
*,
asset_dict: dict[str, Any] | None = None,
full_dict: dict[str, Any] | None = None,
)
Handle for one rendered report asset.
Wraps the asset metadata plus (when fetched via reports.get or
refresh) the json_asset_store content with compile_status, log,
and pdf_b64. Mutators return self for chaining.
compile_log
property
¶
Tectonic stdout/stderr — only available when content has been
fetched (via refresh() or chronicle.reports.get(...)).
compile_status
property
¶
Current compile_status: pending / compiled / failed / stale.
Reads from the asset's asset_config.compile_status (always
present on render-pipeline-produced assets); for content-fetched
reports also cross-references the json_asset_store body.
download_pdf ¶
Download the compiled PDF. If path is given, write it there.
Returns the PDF bytes regardless. Raises RuntimeError if the
compile hasn't succeeded or no PDF is available.
Will fetch content if not already loaded (i.e. one extra round
trip when called on a Report obtained from render rather than
get).
download_source ¶
Return the rendered .tex source. Loads content on first call.
refresh ¶
Re-fetch the report's asset + content. Returns self for
chaining (e.g. report.refresh().compile_status).
wait_for_compile ¶
Poll refresh until compile_status is terminal (compiled,
failed, stale) or timeout elapses. Returns self; check
.compile_status on the result.
On most deployments the render endpoint compiles synchronously, so
this returns immediately on the first refresh. Useful for
deployments where the compile worker is configured to run
out-of-band (or for the M4-style stub where compile_status starts
at pending and never advances — in that case this times out).
Namespaces¶
methodic.ExperimentsAPI ¶
Experiments namespace. Stateless; every method takes the experiment id explicitly.
commit ¶
Commit (lock the spec of) an experiment.
If the experiment has tentative parent lineage links (from being
created/forked off a still-open parent — see
:meth:tentative_links), every one must be resolved here or commit
returns 409: pass tentative_links mapping each parent experiment id
to "promote" (make it real lineage — only valid once the parent has
committed) or "drop" (discard the link). Naming a parent that isn't
a tentative link, or omitting one that is, is rejected — there is no
silent default.
conclude ¶
Conclude an experiment: lock all outputs, no new variations. Terminal.
Not idempotent — re-concluding a concluded experiment raises (409
already_concluded). Gates on a satisfying experiment-level
takeaways_report; if none exists the server schedules a
distillation and the 409 body carries distillation_job_id to poll,
then re-issue conclude.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
experiment_id
|
str
|
Target experiment. |
required |
on_exist_action
|
str | None
|
|
None
|
create ¶
create(
*,
hypothesis_summary: str,
config_yaml: str,
rationale: str | None = None,
description: str | None = None,
accelerate_config_yaml: str | None = None,
launch_config: dict[str, Any] | None = None,
parent_experiment_ids: list[str] | None = None,
allow_retracted_parent: bool = False,
organization_id: str | None = None,
team_id: str | None = None,
visibility: str | None = None,
) -> Experiment
Create a new experiment. Returns a handle with the create-response cached.
Organization scope: pass organization_id (and/or team_id) to
create the experiment under that org/team. Omitting it falls back to
the client's configured default org (the organization_id setting —
see Chronicle); with a default configured, pass methodic.PERSONAL
for a personal experiment owned by the calling key's user.
visibility controls who can read the experiment and its reports:
"private" (creator + org admins only), "organization" /
"team" (the owning org or team gets read + discuss, and its reports
become discoverable in search), or "public" (anyone, read-only).
Omit it for the scope-derived default — org/team-wide in an org
context, private in personal space.
delete ¶
Hard-delete an open (uncommitted) experiment and everything it owns (variations, runs, asset/research-prompt links, ACLs, auto-roles, and best-effort the GitHub repo + search doc). Returns the server's removal summary.
Refused with ConflictError (409) once the experiment is committed
or concluded — retract it instead — or if another experiment was
derived from it (remove the descendants first). Requires the
Delete action. The underlying asset rows survive (they may be
shared across experiments); only this experiment's link rows go.
distill ¶
distill(
experiment_id: str,
*,
scope: str,
variation_id: int | None = None,
corpus_filter: dict[str, Any] | None = None,
write_research_report: bool = True,
reason: str | None = None,
) -> dict[str, Any]
Trigger a distillation agent for the experiment (M9 §17).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
experiment_id
|
str
|
Target experiment. |
required |
scope
|
str
|
|
required |
variation_id
|
int | None
|
Required when |
None
|
corpus_filter
|
dict[str, Any] | None
|
Required when |
None
|
write_research_report
|
bool
|
Whether to also write a
|
True
|
reason
|
str | None
|
Free-form audit string. |
None
|
Returns the 202 body: {distillation_job_id, scope,
expected_outputs, tartarus_instance_id}. Distillation runs
async inside tartarus-d; poll
GET /experiments/{id}/agents to watch progress.
fork ¶
fork(
experiment_id: str,
*,
hypothesis_summary: str,
rationale: str | None = None,
config_yaml: str | None = None,
slug: str | None = None,
allow_retracted_parent: bool = False,
) -> Experiment
Fork an experiment: create a new experiment whose repo mirrors the source's full git history, with a lineage edge to the source. Returns a handle to the new (forked) experiment.
get_agent_config ¶
Read the experiment's agent_config JSON block (M11).
Returns {} for experiments that never had a block set.
Shape per runes/chronicle/designs/agent-flows.md §13:
distillation (default-on auto-trigger + auto-on-conclude
knobs) and continuous_exploration (opt-in synthesis
wake-up loop).
git_status ¶
Current git-integration state for the experiment.
Returns lightweight status info — state (pending/ready/failed/archived),
repo_url (when ready), failure_reason (when failed). Cheap to poll;
UI calls this every couple seconds while state is pending.
iter ¶
iter(
*,
status: str | None = None,
created_by: str | None = None,
page_size: int | None = None,
) -> Iterator[ExperimentSummary]
Yield every experiment matching the filters, paging server-side as needed.
list ¶
list(
*,
status: str | None = None,
created_by: str | None = None,
page_size: int | None = None,
page_token: str | None = None,
) -> ExperimentListPage
One page of experiments matching the filters.
The server paginates on ?limit + ?before=<created_at>_<id> and
returns a bare array (no envelope token); page_size / page_token
are the SDK's stable names for those. The next cursor is derived from
the last row when the page comes back full — a short page is the end.
Use :meth:iter to walk every page.
mint_git_token ¶
Mint a 1-hour install token scoped to this experiment's repo.
The returned token has Administration permission stripped — pushes
to agent/* branches will be rejected by branch protection. Use it
to clone the repo and push to user/... branches you create.
Raises ServerError(503) if the server has no GitHub App configured;
ConflictError(409) if the experiment's repo isn't ready yet.
move ¶
move(
experiment_id: str,
*,
organization_id: str,
team_id: str | None = None,
visibility: str | None = None,
) -> dict[str, Any]
Transfer a personal experiment into an organization.
Personal → org only: the experiment's owner becomes
organization_id (and team_id if given), the org's admins gain
read + administer, and visibility sets who else can read it —
"private" (creator + org admins), "organization" / "team"
(org/team members get read + discuss; the default in an org context),
or "public" (anyone). The original creator keeps their access; the
move only adds org reach (owner_subject is unchanged).
Raises ConflictError (409) if the experiment is already owned by an
org, or if its slug collides with an existing experiment in the target
org (rename it first via PUT /experiments/{id}). Requires
Administer on the experiment and membership of the target org.
promote_lineage ¶
Promote a tentative parent link to real (explicit) lineage.
The deliberate "yes, this fork is real lineage" action: the parent then
appears in :meth:get parent_ids and the lineage DAG. Requires the
parent to have committed (else 409); a tentative link off a still-
open parent is not yet promotable. Idempotent — re-promoting an
already-explicit link succeeds.
record_finding ¶
record_finding(
experiment_id: str,
*,
status: str,
summary: str,
evidence_variation: int,
evidence_run: int | None = None,
source_asset_id: str | None = None,
) -> dict[str, Any]
Record a what's-working / what's-not finding onto the experiment's running summary (issue #357).
The finding is the one-line signal that lands on the experiment
page's running-summary header and the activity feed (a
finding.recorded event). The server keys the summary on
evidence_variation — recording again for the same variation
replaces its finding, so refine freely as reports land. Judge
status from the metrics, not the run's succeed/fail outcome.
Requires Write on the experiment. (MCP-native agents have the
same surface as the chronicle.record_finding tool.)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
experiment_id
|
str
|
Target experiment. |
required |
status
|
str
|
|
required |
summary
|
str
|
The signal in one sentence. |
required |
evidence_variation
|
int
|
The variation whose outcome is the evidence — the upsert key. |
required |
evidence_run
|
int | None
|
Optional specific run within the variation. |
None
|
source_asset_id
|
str | None
|
Optional report asset ( |
None
|
Returns the updated running summary
({version, updated_at, findings: [...]}).
set_agent_config ¶
Replace the experiment's agent_config block.
Validates the continuous_exploration sub-block strictly
(400 with kind: "invalid_continuous_exploration" on
malformed shape); other sub-blocks pass through. Pass {}
to clear all knobs back to defaults.
set_continuous_exploration ¶
set_continuous_exploration(
experiment_id: str,
*,
enabled: bool,
trigger_scope: str = "variation",
cooldown_minutes: int = 0,
) -> dict[str, Any]
Convenience wrapper around :meth:set_agent_config for
the M11 continuous_exploration block.
Merges the new block into the existing agent_config (read
first, then PUT) so other knobs (distillation defaults,
steering caps, etc.) are preserved.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
experiment_id
|
str
|
Target experiment. |
required |
enabled
|
bool
|
Whether the closed-loop wake-up fires on distillation completion. |
required |
trigger_scope
|
str
|
|
'variation'
|
cooldown_minutes
|
int
|
0 .. 1440. |
0
|
tentative_links ¶
The experiment's tentative parent lineage links.
A tentative link is created when an experiment is created/forked off a
parent that is still open (uncommitted). It is excluded from
lineage reads (won't show in :meth:get parent_ids or
:meth:get_lineage) and blocks the child's commit until resolved.
Returns [] for the common case (no tentative links). Resolve each
by :meth:promote_lineage (once promotable) or by passing a
drop/promote disposition to :meth:commit.
wait_for_repo ¶
wait_for_repo(
experiment_id: str,
*,
timeout: float = 300.0,
poll_interval: float = 2.0,
) -> GitStatus
Poll git_status until the repo is ready or failed, or timeout.
methodic.VariationsAPI ¶
Variations namespace. Keys every operation on (experiment_id, variation).
commit ¶
commit(
experiment_id: str,
variation: int,
*,
commit_without_hypothesis: bool = False,
) -> dict[str, Any]
Commit (lock) a variation. Refused (409 missing_variation_hypothesis)
when the variation has no recorded hypothesis unless
commit_without_hypothesis=True — a deliberate, audited choice to
commit without pre-registration.
create ¶
create(
experiment_id: str,
*,
config_yaml: str,
accelerate_config_yaml: str | None = None,
launch_config: dict[str, Any] | None = None,
description: str | None = None,
input_asset_ids: list[str] | None = None,
git_ref: str | None = None,
name: str | None = None,
hypothesis: str | None = None,
expected_outcome: str | None = None,
) -> Variation
Create a new variation under experiment_id. Returns a Variation handle.
hypothesis records the falsifiable hypothesis this variation
validates (tied to the eval metric) — the variation's pre-registration,
the analog of an experiment's hypothesis. expected_outcome is the
predicted result vs. baseline. Both can also be set/refined after
creation via :meth:update while the variation is open. A variation
with no hypothesis can only be committed via the explicit
commit_without_hypothesis override.
git_ref optionally associates the variation with a branch on the
experiment's GitHub repo. Server captures the branch name now;
SHA resolution + the branch-rename-to-agent/... flow happens at
variation commit (Phase 3). Pre-Phase-3, registering with git_ref
is informational only.
list_inputs ¶
List the variation's input assets (each a dict with id,
asset_type, name, …). Use to find a linked code_artifact —
e.g. a bundle to clean up after rebinding the variation to git.
list_outputs ¶
List the variation's output assets across all its runs (each a dict
with id, asset_type, name, state, created_at, …),
newest-first. Use to find produced checkpoints, snapshots, and reports —
e.g. the latest ready checkpoint to resume from.
set_git_ref ¶
Bind a branch to an open variation — records the variation→branch mapping without pinning a SHA (the SHA locks at commit).
Used by the create-first fork flow: create the variation, name the
branch variation/<id>, push it, then bind it here so a tartarus
agent (or the commit-time rename) knows which branch belongs to the
variation. The server rejects the protected agent/* namespace and
returns 409 if the variation is already committed.
unlink_input ¶
Remove an input-asset link from an open variation.
Unlinks the asset from the variation's inputs without deleting the asset
itself — hard-delete the now-orphaned asset separately via
chronicle.assets.delete. Refused with 409 once the variation is
committed (inputs freeze on commit). Used to drop a stale
code_artifact — e.g. a bundle that a later git-ref binding
superseded — while the variation is still open.
update ¶
update(
experiment_id: str,
variation: int,
*,
hypothesis: str | None = None,
expected_outcome: str | None = None,
description: str | None = None,
name: str | None = None,
) -> VariationData
Edit an open variation's mutable metadata. Omitted fields are
left untouched; pass "" to clear one. Raises (409) once the
variation is committed. Primary use: record or refine the
pre-registered hypothesis before commit.
methodic.RunsAPI ¶
Run-lifecycle namespace. Stateless across calls; takes the run triple as args.
fail ¶
Mark a run failed. reason is crash (worker error) or abandoned (cancel).
list_outputs ¶
List the output assets produced by a single run (newest-first).
list_variation_outputs ¶
List output assets produced across all runs of a variation (newest-first). The resume-discovery scope: a fresh run finds the prior run's checkpoint here.
start ¶
start(
experiment_id: str,
variation: int,
run: int,
*,
wandb_run_id: str | None = None,
wandb_entity: str | None = None,
wandb_project: str | None = None,
wandb_dashboard_url: str | None = None,
) -> None
Mark the run started. Optionally link its W&B run by passing the full
triple (wandb_run_id + wandb_entity + wandb_project; project falls
back server-side to the experiment's wandb_project). The link is the
(exp,var,run) → W&B run pointer distillation reads — agent-side with
its own key, or via the backend wandb_fetch_* broker.
methodic.AssetsAPI ¶
AssetsAPI(
transport: Transport,
*,
chronicle: Chronicle | None = None,
default_organization_id: str | None = None,
)
Asset operations.
Output-of linking (which experiment/variation/run produced this asset) is
passed explicitly by callers — Run populates it from its bound context,
while researcher-level uploads pass it directly or omit it for shared assets.
approve ¶
Clear review_required from an asset's pending_reasons.
Auto-finalizes the asset if that was the last reason (state → ready).
Idempotent on already-terminal assets. See design.md § Pending reasons.
Requires Write permission on the asset.
bulk_approve ¶
bulk_approve(
asset_ids: list[str] | None = None,
*,
all_in_scope: bool = False,
) -> BulkApproveResponse
Approve review-gated reports in a batch — the attention feed's "approve selected" / "approve all" action.
Exactly one mode (a ValueError is raised before any request otherwise):
- pass
asset_idsto approve the listed assets ("approve selected"); - pass
all_in_scope=Trueto approve every pendingreview_requiredasset the caller can act on in scope ("approve all N").
Partial success is normal: the call returns HTTP 200 with a per-asset
result list, and a 404 / permission denial / validation failure on one
asset surfaces as an "error" row rather than aborting the batch.
Each asset's Write permission is re-checked server-side, so an
all_in_scope candidate you can read but not write fails its own row.
Returns a :class:~methodic.types.BulkApproveResponse (results +
approved / failed counts).
create_inline ¶
create_inline(
*,
asset_type: str,
content: Any,
name: str | None = None,
content_type: str = "application/json",
output_of: dict[str, Any] | None = None,
asset_config: dict[str, Any] | None = None,
pending_reasons: list[str] | None = None,
organization_id: str | None = None,
team_id: str | None = None,
visibility: str | None = None,
) -> dict[str, Any]
Upload a small inline asset. Chronicle auto-finalizes unless
pending_reasons is non-empty — see design.md § Pending reasons.
Valid reasons: upload_in_progress (not on inline content),
compile_pending, review_required.
Pass organization_id (and/or team_id) to create the dataset
under an org you belong to, and visibility ("private" |
"organization" / "team" | "public") to set who else can read
it — same model as experiments.create. Omitting organization_id
falls back to the client's configured default org (if any); pass
methodic.PERSONAL to force a personal, private dataset.
create_with_presigned ¶
create_with_presigned(
*,
asset_type: str,
components: list[str],
name: str | None = None,
content_type: str = "application/octet-stream",
output_of: dict[str, Any] | None = None,
asset_config: dict[str, Any] | None = None,
pending_reasons: list[str] | None = None,
organization_id: str | None = None,
team_id: str | None = None,
visibility: str | None = None,
) -> AssetUploadInfo
Register a new asset and get presigned PUT URLs for each component.
Accepts an optional asset_config (stored on the asset — datasets put
their provenance record here) and a pending_reasons list (see
create_inline). organization_id / team_id / visibility
set the dataset's org context + visibility (see create_inline);
organization_id falls back to the client's configured default org.
delete ¶
Hard-delete an asset that is not linked to any experiment — no experiment/variation input links and no output links reference it. The cleanup path for orphans (over-uploaded datasets, abandoned pending uploads). Removes the row, its ACLs, inline content, storage bytes, and search document. Returns the server's removal summary. Irreversible.
Refused with ConflictError (409, kind: "asset_linked",
per-table counts in links) while the asset is linked anywhere —
a linked asset is part of an experiment's record; take it out of
use with :meth:deprecate/:meth:invalidate instead. Requires the
Delete action on the asset.
deprecate ¶
Soft-warn on an asset: it stays usable as an input, but the
deprecation (with reason) is surfaced as a warning wherever it
is linked. The right call for "superseded, but existing results
stand". Returns the updated asset.
download ¶
Download all components of an asset to a local directory.
finalize ¶
Mark a presigned-upload asset as ready (immutable) once all components are up.
get ¶
Fetch asset metadata. With include_presigned=True, includes read URLs.
grant_access ¶
Grant a principal (a user sub, team id, org id, or everyone) an
action on a dataset/asset. action defaults to read; others:
write, delete, administer. Idempotent. Requires
Administer on the asset (its creator has it).
invalidate ¶
Hard-block an asset: it can no longer be linked as an input
without allow_invalid_assets. The row, links, and provenance
survive (unlike :meth:delete) — the right call for "wrong data,
do not build on this" when the asset is part of an experiment's
record. Returns the updated asset.
list_access ¶
List the access-control entries on a dataset/asset — who can
read/write it. Requires Read on the asset. Returns the server's
object-aces payload.
move ¶
move(
asset_id: str,
*,
organization_id: str,
team_id: str | None = None,
visibility: str | None = None,
) -> dict[str, Any]
Transfer a dataset/asset into an organization — the asset analog of
experiments.move. Sets its owning org/team so it lists under the org
(GET /assets?owner=…) and bills to it; visibility ("private"
| "organization" / "team" | "public", default org-wide) sets
who else can read it. Requires Administer on the asset and
membership of the target org (resolve the id via
chronicle.me.scopes()). created_by is unchanged.
presign ¶
presign(
asset_id: str,
*,
operation: str = "read",
components: list[str] | None = None,
) -> dict[str, Any]
Request presigned URLs for an asset's components.
reject ¶
Reject a review-gated asset — clears review_required AND
transitions pending → abandoned, recording rejected_at and
rejection_reason. Requires Write on the asset; reason is
a required free-text explanation surfaced in audit + UI.
revoke_access ¶
Revoke a principal's (principal, action) grant on a dataset.
Idempotent — the response removed is False if the grant didn't
exist. Requires Administer on the asset.
set_visibility ¶
Set an asset's visibility — its broadcast read grant — independent of
any experiment it's linked to. visibility is "private" | "org"
(a.k.a. "organization" / "team") | "public".
Use this to share a single report/dataset more widely — e.g. make one
report "public" — without exposing the whole experiment. "private"
removes the broadcast grant; per-person shares from :meth:grant_access
and experiment-inherited access are left untouched (visibility controls
only the single broadcast grant). Unlike :meth:move, this does not
change the asset's owning org. Requires Administer on the asset (its
creator has it; an experiment's admins have it on the experiment's
reports). Returns the asset.
share_with_scope ¶
Share a dataset with a whole team or organization: grant the scope
(a team/org id — see chronicle.me.scopes()) read (default) on
the asset so its members reach it. A thin convenience over
:meth:grant_access; requires Administer on the asset and that you
belong to the scope.
upload_component ¶
PUT one component to its presigned URL.
methodic.SearchAPI ¶
Vertex-backed search across research docs, experiment metadata, and arxiv assets.
history ¶
history(
query: str,
*,
experiment_context: list[str] | None = None,
created_by: str | None = None,
created_after: str | None = None,
created_before: str | None = None,
asset_types: list[str] | None = None,
page_size: int | None = None,
page_token: str | None = None,
) -> SearchResponse
Search Chronicle's INTERNAL corpus — your experiment history and internal research documents (hypothesis/takeaways/research reports, experiment metadata).
This is a semantic alias for :meth:query with the common
narrowing knobs (author, time window, asset type) lifted into
keyword args and assembled into a SearchFilters. It hits the same
POST /search endpoint and is RBAC- and storage-prefix-scoped
server-side.
NOTE: this does NOT search external literature. arxiv / paper search is served by a SEPARATE external MCP, not by this method or by Chronicle's search API.
asset_types defaults to None — Chronicle already excludes session
assets server-side, so the unfiltered corpus is the right default.
Pass a list to narrow to specific types (e.g.
["research_report"]).
iter ¶
iter(
query: str,
*,
filters: SearchFilters | dict[str, Any] | None = None,
experiment_context: list[str] | None = None,
scope: SearchScope | dict[str, Any] | None = None,
page_size: int | None = None,
) -> Iterator[SearchResult]
Yield every search hit, paging server-side as needed.
query ¶
query(
query: str,
*,
filters: SearchFilters | dict[str, Any] | None = None,
experiment_context: list[str] | None = None,
scope: SearchScope | dict[str, Any] | None = None,
page_size: int | None = None,
page_token: str | None = None,
) -> SearchResponse
Run a single search request. Returns one page; use iter to walk pages.
scope is a HARD filter that restricts results to the union of the
named collections + experiments (and member experiments' outputs),
distinct from experiment_context which only boosts. Pass a
:class:~methodic.SearchScope or a raw {collections, experiments}
dict; an empty scope paired with experiment_context hard-scopes to
that context. See collections.md §"Search integration".
methodic.DatasetsAPI ¶
Dataset operations. Reuses AssetsAPI for the asset mechanics and the
transport for the input-link endpoints.
get ¶
Fetch the dataset asset's metadata.
link ¶
link(
asset_id: str,
experiment_id: str,
*,
variation: int | None = None,
propagate_acl: bool = True,
allow_invalid_assets: bool = False,
) -> dict[str, Any]
Link a dataset as an experiment- or variation-level input.
Experiment-level (variation=None) stamps the experiment's auto-roles
onto the asset when propagate_acl (the default) so experiment members
can read it; pass propagate_acl=False for a sensitive dataset.
Variation-level linking ignores propagate_acl — the server does not
propagate ACLs to variation inputs. Either target must be open
(the server returns 409 once committed).
list ¶
list(
*,
n_dims: int | None = None,
min_dims: int | None = None,
max_dims: int | None = None,
precision: str | None = None,
pde_family: str | None = None,
geometry: str | None = None,
min_size: int | None = None,
max_size: int | None = None,
order_by: str | None = None,
owner: str | None = None,
) -> list[dict[str, Any]]
List readable dataset assets with promoted-column filters
(n_dims/min_dims/max_dims, precision, pde_family,
geometry, min_size/max_size) and order_by ∈
{size_bytes, created_at, num_samples}. Cheap Postgres-side
filtering; the Vertex search path (chronicle.search) is the primary
paginated + semantic discovery surface. See datasets.md.
load ¶
Download all of a dataset's components into dest (created if
needed). Returns the destination directory.
provenance ¶
Return the dataset's stored provenance record, or None if absent.
register ¶
register(
components: list[str],
*,
name: str | None = None,
asset_type: str = "dataset",
content_type: str = "application/octet-stream",
provenance: dict[str, Any] | None = None,
output_of: dict[str, Any] | None = None,
organization_id: str | None = None,
team_id: str | None = None,
visibility: str | None = None,
metadata: dict[str, Any] | None = None,
)
Create the dataset asset record + presigned PUT URLs without
uploading bytes — for when you drive the component PUTs yourself
(custom transfer, resumable retries, externally generated data). Returns
the AssetUploadInfo (asset_id, asset_uri, upload_urls); the
caller uploads each component (chronicle.assets.upload_component) and
then finalizes (chronicle.assets.finalize). provenance, if given, is
stored verbatim under asset_config.provenance; metadata is the
dataset metadata document stored under asset_config.metadata. Org
scope + visibility behave as in :meth:upload.
register_by_reference ¶
register_by_reference(
uri: str,
*,
name: str,
metadata: dict[str, Any] | None = None,
size_bytes: int | None = None,
content_type: str = "application/octet-stream",
organization_id: str | None = None,
team_id: str | None = None,
visibility: str | None = None,
) -> dict[str, Any]
Register a dataset whose bytes already live at uri (gs:// or
s3://), uploaded out-of-band — created ready in one call with no
upload step (mirrors external reference assets). metadata is the
dataset metadata document (stored under asset_config.metadata and
projected to the promoted columns + Vertex search facets); size_bytes
is author-declared. Returns the created asset. See datasets.md.
update_metadata ¶
update_metadata(
asset_id: str,
*,
metadata: dict[str, Any] | None = None,
size_bytes: int | None = None,
) -> dict[str, Any]
Update a dataset's mutable descriptive metadata annotation
(asset_config.metadata) and/or size_bytes. The immutable content
(uri / sha256 / components) is untouched; the promoted columns + Vertex
projection are recomputed. Requires Write on the asset. See
datasets.md.
upload ¶
upload(
path: str | Path,
*,
name: str | None = None,
asset_type: str = "dataset",
content_type: str = "application/octet-stream",
source: str | None = None,
provenance: dict[str, Any] | None = None,
output_of: dict[str, Any] | None = None,
link_experiment: str | None = None,
link_variation: int | None = None,
propagate_acl: bool = True,
allow_invalid_assets: bool = False,
organization_id: str | None = None,
team_id: str | None = None,
visibility: str | None = None,
metadata: dict[str, Any] | None = None,
) -> DatasetRef
Upload a dataset file or directory and finalize it.
path is a single file (one component) or a directory (one component
per file — the way to shard GB-scale data). A provenance record is
computed over the bytes and stored on the asset's asset_config.
Pass link_experiment (and optionally link_variation) to link the
finalized dataset as an input in the same call. output_of instead
records the (experiment, variation, run) that produced this dataset —
note that an output_of-linked non-report asset requires Write on
that experiment server-side.
organization_id / team_id / visibility set the dataset's
owning scope + visibility (same model as experiments.create). When
organization_id is omitted and the upload is researcher-initiated
(no output_of), the client's default organization applies;
methodic.PERSONAL forces a personal-scope upload.
methodic.CollectionsAPI ¶
Named, ACL'd, scope-owned groupings of assets + experiments.
Wraps the REST collection endpoints. Authorization, scope resolution, and the existence-only member filtering are applied server-side: callers don't filter for them.
add ¶
add(
collection_id: str,
*,
asset_ids: list[str] | None = None,
experiment_ids: list[str] | None = None,
reindex_mode: str = "lazy",
) -> dict[str, Any]
Add asset and/or experiment members. POST /v1/collections/{id}/members.
Requires Write on the collection and Read on each member; a
member the caller can't read (or that doesn't exist) is skipped with a
warning rather than failing the batch. Idempotent per member. Returns
{added, warnings}.
reindex_mode ("lazy" default / "eager") controls only search
freshness — the Postgres membership is committed either way. Pass
"eager" for a curate-then-search-now flow.
associate ¶
Associate collections with an experiment (boost/scope anchor).
POST /v1/experiments/{id}/collections.
Requires Write on the experiment and Read on each collection; an
unreadable/missing collection is skipped with a warning. Never indexed
(query-time resolution). Returns {associated, warnings}. The agent
flow can associate a collection at experiment setup so later searches
with experiment_context boost — and scope restricts — to it.
create ¶
create(
name: str,
*,
description: str | None = None,
scope_id: str | None = None,
) -> dict[str, Any]
Create a collection. POST /v1/collections.
scope_id is the owning principal (principals.id: a user, team,
or org); omitted, it defaults server-side to the caller's active scope
(a personal collection in a personal context). Returns the created
Collection.
get ¶
Fetch one collection (with member counts). GET /v1/collections/{id}.
The hydrated member_asset_count / member_experiment_count
reflect the caller's visible members (existence-filtered), not the
true totals.
list ¶
list(
*,
scope_id: str | None = None,
q: str | None = None,
include_archived: bool = False,
) -> list[dict[str, Any]]
List collections the caller can Read. GET /v1/collections.
scope_id narrows to one owning scope (defaults to the caller's
personal scope server-side); q is a case-insensitive substring
filter on the name; include_archived includes soft-archived
collections.
members ¶
List the members the caller can independently Read.
GET /v1/collections/{id}/members.
Existence-filtered: a member the caller can't Read is omitted
entirely (no membership oracle). Returns {assets, experiments},
each a list of membership rows.
remove ¶
remove(
collection_id: str,
*,
asset_ids: list[str] | None = None,
experiment_ids: list[str] | None = None,
reindex_mode: str = "lazy",
) -> dict[str, Any]
Remove asset and/or experiment members. DELETE /v1/collections/{id}/members.
Requires Write on the collection (no per-member Read — you can
prune anything in your own collection). Returns {removed}.
reindex_mode governs search freshness only.
methodic.TagsAPI ¶
Tag operations. Stateless; ids are passed explicitly.
asset_tags ¶
Tags applied to an asset. GET /v1/assets/{id}/tags.
create ¶
Find-or-create a tag in a scope. POST /v1/tags.
Idempotent: an existing (scope, normalized-name) returns its
canonical row. scope_id defaults to your personal scope; a team/org
scope requires act-as access. Returns the tag record
({id, scope_id, name, normalized, created_by, created_at}).
delete ¶
Delete a tag (cascades its taggings). DELETE /v1/tags/{id}.
experiment_tags ¶
Tags applied to an experiment. GET /v1/experiments/{id}/tags.
list ¶
List/autocomplete a scope's tags. GET /v1/tags.
q is a prefix matched against the normalized slug; each row carries
a usage_count. scope_id defaults to your personal scope.
objects ¶
The objects a tag is applied to. GET /v1/tags/{id}/objects.
Returns {assets: [...], experiments: [...]}.
rename ¶
Rename a tag in one place. PATCH /v1/tags/{id}.
The new name re-stamps every asset/experiment carrying the tag in the search index (rare). Requires act-as access on the tag's scope.
tag_asset ¶
Apply a tag to an asset. POST /v1/assets/{id}/tags.
Pass tag (a name — find-or-created in the asset's scope) or
tag_id (an existing tag). Requires Write on the asset.
tag_experiment ¶
tag_experiment(
experiment_id: str,
*,
tag: str | None = None,
tag_id: str | None = None,
) -> dict[str, Any]
Apply a tag to an experiment. POST /v1/experiments/{id}/tags.
Pass tag (a name) or tag_id. Requires Write on the
experiment.
untag_asset ¶
Remove a tag from an asset. DELETE /v1/assets/{id}/tags.
untag_experiment ¶
Remove a tag from an experiment. DELETE /v1/experiments/{id}/tags.
methodic.ReportsAPI ¶
Reports namespace. Stateless; every method takes the experiment id
+ report kind explicitly. Most users access via exp.reports instead
of calling these methods directly.
get ¶
Fetch a report by its asset id. Returns the asset metadata plus content (compile_status, log, pdf_b64 if compiled).
render ¶
render(
experiment_id: str,
kind: str,
*,
payload: dict[str, Any] | None = None,
tex_body: str | None = None,
template_asset_id: str | None = None,
) -> Report
Render (and compile, when chronicle-tex is configured) a report.
Pass payload for template mode; tex_body for freeform mode.
Mode is selected by the experiment's report_settings.{kind}.mode
— these args feed the chosen mode and are ignored otherwise.
update_settings ¶
Replace experiment.report_settings with settings. Frozen at
commit — server returns 409 once the experiment is committed.
methodic.ResearchPromptsAPI ¶
Research prompts namespace. Stateless; every method takes ids explicitly.
Most users access the per-experiment view via exp.research_prompts
instead of calling these methods directly.
attach ¶
Associate an existing research prompt with an experiment.
Set primary=True to make it the experiment's primary prompt.
Requires Write on the experiment. Returns
{experiment_id, research_prompt_id, is_primary}.
create ¶
Create a research prompt and return the created record.
The server stores the prompt and reads it back, so the response is
the full record: {id, prompt, created_at, created_by} (HTTP
201). Prompts are immutable once created.
The create endpoint takes only the prompt text — it does not link
to experiments. Pass experiment_ids to attach the new prompt to
those experiments in follow-up calls (none of them primary; call
:meth:attach with primary=True if you need a primary link).
get ¶
Fetch a research prompt by id. Returns {id, prompt,
created_at, created_by}; raises NotFoundError if it doesn't
exist.
list_for_experiment ¶
List the research prompts associated with an experiment.
Returns a list of {research_prompt: {...}, is_primary: bool}
records (at most one has is_primary: true). Requires Read on
the experiment.
methodic.imports.ImportsAPI ¶
ImportsAPI(
transport: Transport,
assets: AssetsAPI,
*,
default_organization_id: str | None = None,
)
Org-library document imports. Organization scope is mandatory — these are never personal uploads (the server refuses them).
research_reports ¶
research_reports(
paths: list[Path | str] | Path | str,
*,
organization_id: str | None = None,
team_id: str | None = None,
visibility: str | None = None,
import_source: str | None = None,
collection: str | None = None,
collections: list[str] | str | None = None,
finalize: bool = True,
) -> ImportSummary
Import one or more research-report PDFs into organization_id
(or the client default org). Directories expand to their *.pdf
files (non-recursive); explicitly-named non-PDF files raise.
Per file: register (imported_report, sha256/size provenance) →
presigned PUT → collect. Then one /assets/bulk-finalize over
the batch verifies the uploads, flips them ready, and enqueues the
extraction job. Duplicates within the org report as
duplicates — re-running a batch is safe.
collection / collections (fluent — pass either or both, each
an id or a slug) target one or more collections: every report that
reaches ready is added as a member at finalize (the server resolves
slugs within the org scope and requires Write on each collection —
an unresolvable slug or missing grant fails the finalize call). A
collection a report could not be added to (e.g. Write lost mid-batch)
is reported per-asset in collection_warnings, never failing the
batch.
methodic.errata.ErrataAPI ¶
Create and list errata. Paths are version-free; Transport prepends
/v1.
create ¶
create(
*,
experiment_id: str | None = None,
variation: int | None = None,
asset_id: str | None = None,
markdown: str | None = None,
latex: str | None = None,
pdf: str | Path | bytes | None = None,
name: str | None = None,
) -> dict[str, Any]
Create an erratum and attach it to its target in one call.
Exactly one of markdown/latex (authored) or pdf
(imported) must be given. Returns the hydrated erratum
({asset, target, created_by, created_at}). Requires Write on
the target.
delete ¶
Detach an erratum from its target. Requires Write on the target.
list ¶
list(
*,
experiment_id: str | None = None,
variation: int | None = None,
asset_id: str | None = None,
) -> list[dict[str, Any]]
List the errata correcting a given object (chronological).
methodic.PublicationsAPI ¶
Publication operations. Stateless; ids are passed explicitly.
finalize ¶
finalize(
publication_id: str,
*,
doi: str | None = None,
bibtex: str | None = None,
) -> dict[str, Any]
Finalize a draft publication. POST /v1/publications/{id}/finalize.
Promotes the draft in place to a registered, public, system-owned record
(resolving doi/bibtex). The asset id is unchanged, so existing
citation links stay intact. Only a draft you can Write may be
finalized; registered publications are immutable. Returns
{"publication": {...}}.
get ¶
Fetch a publication record (an asset). GET /v1/assets/{id}.
register ¶
register(
*,
doi: str | None = None,
bibtex: str | None = None,
draft: bool = False,
confirm_create: bool = False,
) -> dict[str, Any]
Register (or dedup to) a publication. POST /v1/publications/register.
Pass doi (resolved via Crossref, falling back to doi.org) or
bibtex. A known DOI dedups to the existing record. Returns
{"publication": {...}, "existing": bool}; for a BibTeX entry with no
DOI that matches existing records, returns
{"status": "needs_resolution", "parsed": {...}, "candidates": [...]}
— pick one (link it as a citation) or re-call with
confirm_create=True to mint a new record.
draft=True registers a private, mutable placeholder for unpublished
work (owned by you); :meth:finalize it later.
search ¶
Search registered publications by title. GET /v1/publications.
Returns the list of matching publication assets (newest first). Useful
before registering, to reuse an existing record. (Full-text/semantic
search rides chronicle.search with asset_types=["publication"].)
methodic.PendingAPI ¶
The caller's attention queue (blocked + report_approval).
iter ¶
Yield every pending item, paging server-side as needed.
list ¶
list(
*,
limit: int | None = None,
before: str | None = None,
scope: str | None = None,
) -> PendingPage
One page of pending items, newest-first.
The server paginates on ?limit + ?before=<since>_<id> and
returns a bare array; next_page_token on the result is built from
the last row when the X-Has-More header says more remain. Pass it
back as before for the next page, or use :meth:iter to walk them
all.
scope is reserved for forward compatibility. The v1 endpoint derives
the scope from the calling key/user's readable set (there is no
scope/owner query parameter yet), so a value is accepted but not sent;
narrow by issuing the call with a scope-restricted key instead.
methodic.ActivityAPI ¶
The caller's reverse-chronological event stream.
iter ¶
Yield every activity event, paging server-side as needed.
list ¶
One page of activity events, newest-first.
The server paginates on ?limit + ?before=<timestamp>_<id> and
returns a bare array; next_page_token on the result is built from
the last row when the X-Has-More header says more remain. Pass it
back as before, or use :meth:iter to walk every page.
methodic.ApiKeysAPI ¶
chronicle.api_keys — API key lifecycle.
Keys are owner-scoped: every operation acts on the calling principal's
own keys. The restriction on a key narrows it below the owner's
authority (never above); a restricted calling key can only mint/edit
keys within its own restriction (the server enforces the subset rule).
create ¶
create(
name: str,
*,
key_type: str = "agent",
expires_at: str | None = None,
restriction: dict[str, Any] | None = None,
) -> dict[str, Any]
Mint a new API key. Returns the full record including the
secret (key) — shown exactly once; store it now.
key_type is "agent" (automation) or "user".
restriction is the authority ceiling (see
:func:org_ceiling_restriction); omit for full owner authority.
create_org_bound ¶
create_org_bound(
name: str,
organization_id: str,
*,
key_type: str = "agent",
expires_at: str | None = None,
) -> dict[str, Any]
Mint a key restricted to mutating only inside organization_id
(read stays broad) — the org-context shape. Convenience over
:meth:create with :func:org_ceiling_restriction.
list ¶
Every API key you own — each {id, name, key_type, created_at,
expires_at, restriction?}. The id is the handle for
:meth:revoke, :meth:rotate, :meth:set_restriction.
revoke ¶
Revoke a key by its id. Immediate — any agent using it is locked out at once.
rotate ¶
Single-step rotation: same key row (id, name, restriction), fresh
secret. The old secret stops validating immediately; the new key
is returned once. Use when a key leaks or its setup snippet is
lost — its identity and grants are unchanged.
set_restriction ¶
Replace a key's restriction (Cloudflare-style permission
editing). None clears it (full owner authority) — only allowed
when the calling credential is itself unrestricted.
methodic.FeedbackAPI ¶
Feedback + error-report namespace. Stateless and submit-only.
report_error ¶
report_error(
error_type: str,
message: str,
stack: str | None = None,
request_method: str | None = None,
request_path: str | None = None,
response_status: int | None = None,
) -> None
Report a reproducible error to the bug pipeline. Returns None.
Posts to /v1/errors with source: "sdk" and captured_at
stamped now (UTC, ISO 8601). The endpoint is fire-and-forget: the
server replies 202 Accepted (with the stored report's id and its
own fingerprint), which this method discards. HTTP failures still
raise the usual APIError subclasses.
A client-side fingerprint is included for stable cross-report
identity: the SHA-256 hex of error_type | message | top stack
frames, where the top 5 non-empty stack lines are normalized by
stripping line/column numbers, hex addresses, and {{closure}}#N
ids (design §4.4 — volatile details shouldn't fork the identity; the
same failure reported twice yields the same fingerprint). The server
recomputes its own fingerprint from the sanitized fields and treats
the client value as advisory.
request_method / request_path / response_status carry the
HTTP context when the error came from an API call. Prefer a route
template for request_path (/experiments/{id}/git, not a
literal path with ids) — the server only strips obvious UUID/integer
segments as a backstop.
submit ¶
submit(
body_md: str,
category: str = "feedback",
context: dict[str, Any] | None = None,
source: str = "sdk",
) -> str
Submit plain (non-bug) feedback. Returns the created feedback id.
body_md is Markdown by contract — tables, fenced code blocks,
and lists are all fine; operators read it rendered (sanitized GFM) in
the admin bridge. Size-capped server-side at 64 KiB.
category is one of "feedback" (impression/UX), "gap" ("the
surface can't express what I needed"), or "feature_request"
(explicit ask). Anything else raises ValueError before any request
is made.
context is identifiers only — e.g. {"experiment_id": ...,
"skill": ..., "sdk_method": ...}. Never put bodies, transcripts, or
secrets in it; it exists to make the feedback findable, not to carry
payload.
source records which client surface filed the report ("sdk"
by default; skills pass "skill").
Reproducible errors are not feedback: if you have a failing call
with a message/stack, use :meth:report_error so it gets
fingerprinted and triaged by the bug pipeline.
methodic.me.MeAPI ¶
/v1/me/* — facts about the calling key's own principal.
Currently just scope discovery; the natural home for /v1/me profile and
default-org reads as they're needed by skills.
scopes ¶
Every scope you can operate as: your personal space plus every team
and organization you belong to (directly or transitively). Each carries
id, kind ("user" | "team" | "organization"),
name, and slug.
Use it to resolve an org/team the user named to the id that
:meth:ExperimentsAPI.move and experiments.create(organization_id=…)
take, rather than hard-coding a UUID::
org = next(
s for s in chronicle.me.scopes()
if s.kind == "organization" and s.slug == "acme"
)
chronicle.experiments.move(exp_id, organization_id=org.id)
Response & data types¶
methodic.ExperimentData
dataclass
¶
ExperimentData(
id: str,
owner_subject: str,
hypothesis_summary: str,
created_at: str,
created_by: str,
state: str,
rationale: str | None = None,
description: str | None = None,
committed_at: str | None = None,
concluded_at: str | None = None,
retracted_at: str | None = None,
retraction_reason: str | None = None,
git_repo_state: str = "pending",
git_repo_url: str | None = None,
git_repo_failure_reason: str | None = None,
)
Mirror of the server's Experiment struct.
git_repo_state defaults to "pending" so older server payloads (which
may not include the field yet) deserialize cleanly.
methodic.ExperimentDetail
dataclass
¶
ExperimentDetail(
experiment: Experiment,
parent_ids: list[str],
variations: list[VariationSummary],
)
GET /experiments/{id} response: experiment + parents + variation summaries.
methodic.ExperimentSummary
dataclass
¶
ExperimentSummary(
id: str,
hypothesis_summary: str,
variation_count: int,
created_at: str,
created_by: str,
state: str,
status: str | None = None,
committed_at: str | None = None,
concluded_at: str | None = None,
retracted_at: str | None = None,
)
One row in the experiments list.
methodic.ExperimentListPage
dataclass
¶
One page of experiments.list results plus a cursor for the next page.
The current server returns a flat array; we normalize that into a
single-page response with next_page_token=None. When the server
grows pagination, the same dataclass keeps working.
methodic.VariationData
dataclass
¶
VariationData(
experiment_id: str,
variation: int,
config_json: dict[str, Any],
config_yaml: str,
created_at: str,
created_by: str,
state: str,
accelerate_config_json: dict[str, Any] | None = None,
accelerate_config_yaml: str | None = None,
launch_config: dict[str, Any] | None = None,
description: str | None = None,
committed_at: str | None = None,
retracted_at: str | None = None,
retraction_reason: str | None = None,
git_ref: str | None = None,
git_sha: str | None = None,
name: str | None = None,
hypothesis: str | None = None,
expected_outcome: str | None = None,
)
Mirror of the server's Variation struct.
config_json, accelerate_config_json, and launch_config arrive as
arbitrary JSON — kept as dict[str, Any] since the schema is open.
methodic.VariationSummary
dataclass
¶
VariationSummary(
variation: int,
created_at: str,
run_count: int,
state: str,
description: str | None = None,
latest_status: str | None = None,
committed_at: str | None = None,
retracted_at: str | None = None,
name: str | None = None,
hypothesis: str | None = None,
)
One variation as it appears in ExperimentDetail.variations.
methodic.CreateExperimentResponse
dataclass
¶
CreateExperimentResponse(
experiment_id: str,
variation: int,
run: int,
tentative_parents: list[TentativeParentLink] = list(),
)
POST /experiments response: the new experiment plus the always-created variation 0 / run 0.
tentative_parents is non-empty only when the experiment was created
with a parent that was still open — see :class:TentativeParentLink.
Empty for the common (no-parent / committed-parent) case.
methodic.LineageResponse
dataclass
¶
methodic.UpstreamRetraction
dataclass
¶
UpstreamRetraction(
experiment_id: str,
retracted_at: str,
reason: str,
depth: int,
variation: int | None = None,
document_asset_id: str | None = None,
chain: list[str] | None = None,
)
methodic.UpstreamRetractionsResponse
dataclass
¶
methodic.SearchResult
dataclass
¶
SearchResult(
document_id: str,
source_type: str,
relevance_score: float,
lineage_boost: bool,
asset_type: str | None = None,
title: str | None = None,
snippet: str | None = None,
experiment_ids: list[str] = list(),
created_at: str | None = None,
)
One hit from the Vertex-backed search.
methodic.SearchResponse
dataclass
¶
methodic.SearchFilters
dataclass
¶
SearchFilters(
asset_types: list[str] | None = None,
organization_id: str | None = None,
team_id: str | None = None,
created_after: str | None = None,
created_before: str | None = None,
created_by: str | None = None,
source_type: str | None = None,
tags: list[str] | None = None,
)
Filters layered on top of the RBAC + namespace filters the server adds.
methodic.SearchScope
dataclass
¶
A hard scope filter for search — distinct from experiment_context,
which only boosts.
When set, results are restricted to the resolved union of the named
collections and experiments, ANDed with the server's RBAC
allowed_readers filter. Maps to chronicle_core::types::SearchScope
and rides POST /v1/search as a top-level scope (a sibling of
filters).
"Scope to my current experiment" convenience: pass an empty
SearchScope() together with experiment_context to flip those anchors
from boost to hard-scope (the server falls back to experiment_context
when scope is present but both vectors are empty). See
runes/chronicle/designs/collections.md §"Search integration".
methodic.AssetUploadInfo
dataclass
¶
Result of AssetsAPI.create_with_presigned: where to put each component.
methodic.DatasetRef
dataclass
¶
DatasetRef(
asset_id: str,
asset_uri: str,
name: str,
asset_type: str,
components: list[str],
provenance: dict[str, Any],
)
Result of DatasetsAPI.upload: the finalized dataset asset + its provenance.
methodic.GitStatus
dataclass
¶
GitStatus(
state: str,
default_branch: str = "main",
repo_url: str | None = None,
failure_reason: str | None = None,
branches: list[GitBranch] = list(),
)
Response from GET /experiments/{id}/git.
state mirrors Experiment.git_repo_state. When state == "ready",
repo_url is populated; when state == "failed", failure_reason is
populated. branches is empty until branch enumeration is wired up
server-side (Phase 2 follow-up).
methodic.GitToken
dataclass
¶
1-hour GitHub installation access token returned by experiments.mint_git_token.
token is what callers paste into git as
https://x:<token>@github.com/<org>/<repo>. Per the design, the
token has Administration permission stripped server-side, so it cannot
push to agent/* branches or modify branch protection — only the
Chronicle App can do those.
methodic.GitBranch
dataclass
¶
One branch on the experiment repo, returned by experiments.git_status.
methodic.PendingItem
dataclass
¶
PendingItem(
kind: str,
id: int,
since: str,
variation: int = 0,
experiment_id: str | None = None,
experiment_slug: str | None = None,
experiment_title: str | None = None,
owner_principal_id: str | None = None,
agent_id: str | None = None,
steering_asset_id: str | None = None,
asset_id: str | None = None,
asset_type: str | None = None,
asset_title: str | None = None,
)
One actionable item from GET /v1/pending — something awaiting the
caller's action across every object they can read.
kind is "blocked" (a variation whose agent paused for user/coordinator
input — act via the experiment's steering_asset_id) or
"report_approval" (a review-gated report awaiting approve/reject — act on
asset_id via :meth:AssetsAPI.approve / :meth:AssetsAPI.reject or
:meth:AssetsAPI.bulk_approve).
id is a stable per-row display id (queue seq for blocked, a derived
non-zero int for report_approval) — not the approve/reject identity
(use asset_id) and not a standalone cursor (the endpoint paginates on
since). variation is the variation index for blocked and 0 for
report_approval. The kind-specific fields (agent_id /
steering_asset_id for blocked; asset_id / asset_type /
asset_title for report_approval) are absent on the other kind. since is
the cursor timestamp (suspend transition for blocked, the asset's
created_at for report_approval).
methodic.PendingPage
dataclass
¶
One page of pending.list results plus a cursor for the next page.
The server returns a bare array and signals more via the X-Has-More
response header (it over-fetches one row to decide). The SDK reads that
header and, when more remain, builds next_page_token from the last
item's (since, id) as <since>_<id> — the ?before cursor the
server consumes (it keys on the since half). next_page_token is
None on the last page. Use :meth:PendingAPI.iter to walk every page.
methodic.ActivityItem
dataclass
¶
ActivityItem(
id: int,
timestamp: str,
action: str,
resource_type: str,
resource_id: str,
actor_subject: str,
via: str | None = None,
status: str | None = None,
experiment_id: str | None = None,
)
One event from GET /v1/activity — the caller's reverse-chronological
feed of audited actions across the experiments they can read.
action is the audited action (e.g. run.start, asset.create,
experiment.conclude); resource_type / resource_id identify the
object it touched; actor_subject is who did it. via (optional) records
how the action was issued (e.g. a key/instance attribution), status is the
outcome where applicable, and experiment_id is the related experiment when
one applies. timestamp is the cursor key.
methodic.ActivityPage
dataclass
¶
One page of activity.list results plus a cursor for the next page.
Same wire shape as pending: a bare array + an X-Has-More header. The
next ?before cursor is <timestamp>_<id> built from the last item.
next_page_token is None on the last page. Use
:meth:ActivityAPI.iter to walk every page.
methodic.BulkApproveResponse
dataclass
¶
Response from assets.bulk_approve.
Partial success is normal — the call is always HTTP 200 (only auth/parse
failures are non-200), and a per-asset failure surfaces as an "error"
row rather than aborting the batch. approved / failed are the per-status
counts over results.
methodic.BulkApproveResult
dataclass
¶
BulkApproveResult(
asset_id: str,
status: str,
error: str | None = None,
finalized: bool | None = None,
)
Per-asset outcome within a BulkApproveResponse.
status is "approved" or "error". On error, error carries a
human-readable reason and finalized is None. On success, finalized
reports whether the approval auto-transitioned the asset pending → ready
(True) or it still carries other pending reasons (False).
methodic.imports.ImportSummary
dataclass
¶
ImportSummary(
organization_id: str,
imported: list[ImportedReport] = list(),
duplicates: list[str] = list(),
failed: list[tuple[str, str]] = list(),
extraction_job_id: str | None = None,
collection_warnings: list[dict[str, Any]] = list(),
)
Outcome of an import batch — counts always add up to the inputs.
methodic.imports.ImportedReport
dataclass
¶
One registered-and-uploaded PDF.
methodic.types.Scope
dataclass
¶
One scope the caller can operate as — their personal space, or a team or
organization they belong to. Mirrors the server's UserScope.
kind is "user" | "team" | "organization". id is the
principal id you pass as organization_id / team_id elsewhere (e.g.
experiments.move(...)). slug may be empty for a sparse personal scope
that has no homepage yet. For a team scope, organization_id is the
team's parent organization (a team always sits inside one org); None
for user + organization scopes — so a key/experiment created under a team
can record both organization_id and team_id.
Errors¶
methodic.ChronicleError ¶
Bases: Exception
Base class for every error raised by the methodic client.
methodic.APIError ¶
methodic.AuthenticationError ¶
methodic.PermissionDeniedError ¶
methodic.NotFoundError ¶
methodic.BadRequestError ¶
methodic.ConflictError ¶
methodic.ServerError ¶
methodic.ChronicleConfigError ¶
Bases: ChronicleError
Client configuration could not be resolved — a missing API key, or a
malformed config file / environment value. Raised by Chronicle.from_env
and Chronicle.from_file before any HTTP request is made.