AI Decision System
Replayable, offline-verifiable records for AI-assisted go/no-go decisions: experiment promotions, typed model answers and human-approved actions.
MTHDS는 AI 방법을 정의·패키징·공유하는 오픈 표준으로, 에이전트가 구조화되고 조합 가능한 AI 워크플로를 탐색·실행할 수 있게 한다
MTHDS is an open standard for defining, packaging, and sharing AI methods — giving agents the ability to discover and execute structured, composable AI workflows.
개발 도구TypeScript · JavaScriptGitHub @mthds-ai온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
CLI of MTHS open standard https://mthds.ai
저장소 README에서 가져옴
README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
CLI and SDK for methods — reusable workflows for AI coding agents.
The MTHDS open standard is defined at mthds.ai. Browse and discover public methods on the hub at mthds.sh.
A method is a packaged workflow that an AI agent (like Claude Code) can use. Methods are stored in a registry and installed locally via their unique name.
1. Browse methods on the hub at mthds.sh
3. Install a runner to execute methods. Pipelex is the reference runner (Python):
For the full CLI reference, see CLI.md.
To install a single method from a multi-method repository:README 전문 보기 ↗CLI and SDK for methods — reusable workflows for AI coding agents.
The MTHDS open standard is defined at mthds.ai. Browse and discover public methods on the hub at mthds.sh.
A method is a packaged workflow that an AI agent (like Claude Code) can use. Methods are stored in a registry and installed locally via their unique name.
Browse methods on the hub at mthds.sh
Install a method:
mthds install org/repo --method my-method
mthds runner setup pipelex
This installs pipelex and runs pipelex init, which first asks where your runs should execute. Its default answer, taken by pressing Enter, is the hosted Pipelex API, for which it gets a Pipelex API key through your browser; answer 2 (this machine) to run methods locally with your own provider keys instead.
mthds run method my-method
For the full CLI reference, see CLI.md.
npx mthds install org/repo-name
To install a single method from a multi-method repository:
npx mthds install org/repo-name --method my-method
The CLI will:
methods/ folder from the GitHub repositoryMETHODS.toml manifest--method <name> is provided, install only that method (errors if name not found).mthds/methods/<name>/: its METHODS.toml, its .mthds files and every other file the MTHDS standard counts as part of the method, such as its Python, templates and images, each with its exact bytes. Hidden files and folders, __pycache__, node_modules, virtual environments, nested repositories and packages, a run's inputs file named inputs.json or inputs.toml, and whatever a .gitignore or the manifest's exclude ignores stay behind. The tree is validated whole before it is installed, and a reinstall replaces the method's directory.You can also install from a local directory:
npx mthds install --local /path/to/repo
| Location | Path |
|---|---|
| Local | <cwd>/.mthds/methods/<name>/ |
| Global | ~/.mthds/methods/<name>/ |
To make your methods installable via npx mthds install, you need a public GitHub repository with the right structure. Push it, then run mthds publish org/repo-name: mthds.sh reads each method from the repository's default branch and lists it, and the command shows each method's page or the reason it was refused. See docs/publishing.md.
org/repo-name (or user-name/repo-name)
└── methods/
├── my-method/
│ ├── METHODS.toml
│ ├── main.mthds
│ └── helpers/
│ └── utils.mthds
└── another-method/
├── METHODS.toml
└── pipeline.mthds
org/repo-name, user-name/repo-name, or the full URL https://github.com/org/repo-namemethods/ folder at its rootmethods/, each subfolder is a method packageMETHODS.toml file that follows the manifest specification.mthds files (the actual method definitions), and may hold any other file its pipes use, such as Python, templates and images, which are installed with itThe METHODS.toml manifest is validated during installation. A minimal valid manifest:
[package]
name = "your-method"
address = "github.com/your-org/your-repo"
version = "1.0.0"
description = "A short description of what this method does"
Optional fields: display_name, authors, license, mthds_version.
See the full specification at mthds.ai/latest/packages/manifest.
The CLI validates everything during install:
METHODS.toml must parse as valid TOML[package] section with name, address, version (semver), and description are requiredaddress must include a hostname with a dot (e.g. github.com/...)mthds_version, if present, must be a valid version constraint and must be satisfied by the MTHDS standard version this CLI implements — a method declaring itself incompatible is refused rather than installed. See docs/versioning.mdTo execute a method, you need a runner. A runner is the engine that takes a method definition and actually runs it.
| Runner | Description |
|---|---|
| Pipelex | A Python-based runner you install on your machine. It runs methods on this machine with your own provider keys, or on the hosted Pipelex API with a Pipelex API key, as pipelex is set up. Install it with npx mthds runner setup pipelex. |
| Pipelex API (remote) | An API server that runs methods remotely. You can self-host it using pipelex-api, the source-available API server released with Pipelex. The Pipelex Hosted API lives at https://api.pipelex.com. |
These are the only runners that exist today. Feel free to create your own runner in a different language!
npx mthds runner setup pipelex
This installs pipelex and runs pipelex init, whose answer to "Where should your runs execute?" becomes pipelex's [run] execution setting: local runs methods on this machine with your own provider keys, and hosted, the default answer, runs them on the hosted Pipelex API. A single pipelex run can go the other way with --hosted or --local.
Hosted runs through pipelex use the Pipelex API key in PIPELEX_API_KEY, which mthds login (or pipelex login) gets through the browser and saves to ~/.pipelex/.env. They need pipelex 0.79.0 or later, and they send only a method's .mthds files, so a method whose pipes call custom Python cannot run that way. See CLI.md.
The pipelex runner is the default; to call the Pipelex Hosted API or a self-hosted API directly instead, set up the API runner interactively:
mthds runner setup api
This prompts for the API base URL and an API key (masked input), and saves them to ~/.mthds/config.
The two runners keep their keys apart. The API runner reads its api-key (MTHDS_API_KEY) from ~/.mthds/config, and the pipelex runner's hosted runs read PIPELEX_API_KEY from pipelex's ~/.pipelex/.env, which mthds login writes. Setting one does not set the other.
There is ONE base URL — the host only, with no version prefix. The SDK composes every endpoint as {base}/v1/{endpoint}:
base-url — Pipelex Hosted API: https://api.pipelex.com (the default). Self-hosted: http://localhost:8081 (a bare pipelex-api runner).You can also set values directly:
mthds config set api-key YOUR_KEY
# Pipelex Hosted API (default):
mthds config set base-url https://api.pipelex.com
# Self-hosted bare runner:
mthds config set base-url http://localhost:8081
Configuration is stored in ~/.mthds/config and shared between mthds-js and mthds-python.
You can also use environment variables (MTHDS_API_KEY, MTHDS_BASE_URL) which take precedence over the config file.
See the SDK Usage section below to connect to a Pipelex API instance programmatically.
Install the package:
npm install mthds
import { MthdsApiClient } from "mthds";
const client = new MthdsApiClient({
baseUrl: "https://api.pipelex.com",
apiKey: "your-api-key",
});
const result = await client.execute({
pipe_code: "my-pipeline",
inputs: {
topic: "quantum computing",
},
});
console.log(result.pipe_output);
The base URL is the host only — every endpoint composes as {baseUrl}/v1/{endpoint} (e.g. https://api.pipelex.com/v1/execute); /health resolves to the origin root.
Point the client at your own pipelex-api instance — the same MTHDSProtocol surface, same paths:
const client = new MthdsApiClient({
baseUrl: "http://localhost:8081",
apiKey: "your-api-key",
});
Use execute (blocking) or start (completion delivery is implementation-defined — see your runner's API documentation). The durable run-lifecycle (poll a run by id) is not part of this client — it lives in the Pipelex runtime SDK (@pipelex/sdk).
Note: the bare-runner blocking path returns the runner's native
pipe_output, whereas the Pipelex Hosted API durable path returnsmain_stuff+graph_spec. Cross-shape normalization is a v1 TODO.
Every request carries a User-Agent header (mthds-js/<version> node/<version> (<os>; <arch>)) so the hosted API can attribute traffic to the program that sent it; none is set in a browser. Put your own program's name in front of it with appInfo:
const client = new MthdsApiClient({
apiKey: "your-api-key",
appInfo: { name: "acme-invoicer", version: "1.4.0", url: "https://acme.example" },
});
// User-Agent: acme-invoicer/1.4.0 (+https://acme.example) mthds-js/<version> node/<version> (<os>; <arch>)
name and version must be RFC 9110 tokens; an invalid value throws a TypeError at construction. See docs/client-identification.md.
Instead of passing options to the constructor, you can set environment variables:
| Variable | Description |
|---|---|
MTHDS_BASE_URL | API base URL — host only, no version prefix (default https://api.pipelex.com) |
MTHDS_API_KEY | API authentication token |
// Reads MTHDS_BASE_URL and MTHDS_API_KEY from the environment
const client = new MthdsApiClient();
The client implements the MTHDS Protocol plus the Pipelex API's pipe I/O extension. (The durable run-lifecycle — poll a run by id — lives in @pipelex/sdk, not here.)
| Method | Route | Description |
|---|---|---|
execute(options) | POST /v1/execute | Execute a method and wait for the result — returns a RunResultExecute (throws RunStillRunningError on the protocol's optional 202 degrade) |
start(options) | POST /v1/start | Start a method asynchronously — returns a RunResultStart with the authoritative pipeline_run_id |
validate(source, allowSignatures?, render?) | POST /v1/validate | Parse, validate, and dry-run a method's tree, given as { files } or { tree } — returns the protocol's ValidationResult discriminated on is_valid; an invalid method is a produced verdict (is_valid: false with its faults in errors, each a problem item naming its file by tree path in location.file), not a throw. Only a no-verdict non-2xx (malformed request, auth, server fault) throws ApiResponseError |
models(category?) | GET /v1/models | The model deck the runner routes to |
pipeIo(request) | POST /v1/pipe-io | A method's pipe I/O contracts, input form and output form in one call, with no dry run (a Pipelex API extension) — returns a PipeIOResponse discriminated on is_valid. The method is its tree, given as { tree } or { files } and sent as validate sends it, a method_ref address or a hosted method_id; a refused pipe selection is a 422 whose problem's code is one of PIPE_SELECTION_CODES. See docs/pipe-io.md |
version() | GET /v1/version | Protocol + implementation versions (the feature-detection handshake) |
findMissingBlobs(request) | POST /v1/blobs/missing | Which blobs of a tree the runner's blob store lacks, each with its upload target — the step before sending a tree by hash, which , , and take themselves when given a |
findMissingBlobs raises MalformedBlobsAnswerError, a ProblemError of client.response_unreadable naming the faulty entry, when the runner's answer is not shaped as the protocol says. The client keeps the runner's handshake for HANDSHAKE_TTL_MS, and asks it again sooner when a refusal shows it to be stale.
Every failure is a ProblemError carrying problem, the MTHDS Protocol's error document coded from the core error registry, which this package vendors and types. Branch on err.problem.code, and on domain, retry and next_step.kind when several codes are handled alike:
import { ProblemError } from "mthds/errors";
try {
await client.execute({ pipe_code: "summarize", inputs });
} catch (err) {
if (!(err instanceof ProblemError)) throw err;
if (err.code === "client.outcome_unknown") {
// The run may have started: look at the run history before starting it again.
} else if (err.retry === "yes") {
// Repeat after err.retryAfter seconds, when given.
} else {
console.error(err.message); // the problem's sentence, then its next step
}
}
An answer of the runtime is read as the problem it carries (ApiResponseError), or, when its body is none, as the one core code of its status or client.response_unreadable; a 202 on execute is client.follow_failed (RunStillRunningError); no answer at all is client.unreachable, client.timeout or, after a request that creates a run left, client.outcome_unknown (ApiTransportError). See docs/errors.md.
| Option | Type | Description |
|---|---|---|
pipe_code | string | Pipe code to execute |
files | MethodFiles | The method's tree, as a map from tree path to file entry: a text, { base64 } or { sha256, size } |
tree | MethodTreeSource | The method's tree to send, such as the MethodTree that listMethodTree returns; the client sends it inline or by hash, as the runner allows. Exclusive with files |
inputs | Record<string, string | string[] | object> | Method input variables |
output_name | string | Name of the output to return |
output_multiplicity | boolean | number | Expected output multiplicity |
dynamic_output_concept_ref | string | Dynamic output concept reference |
extra | Record<string, unknown> | Server-specific extension args, forwarded verbatim into the request body (e.g. a stored-method id) |
Either pipe_code or the method's tree (files or tree) must be provided (or a server-specific extension arg via extra). A tree holds at least one file, so an empty files map or tree is refused, and extra may carry neither a protocol field nor the client-side tree and bundleMain. A tree of 256 KiB or less, measured as the JSON of its inline map, travels inline; a larger one travels by hash when the runner keeps a blob store, uploading only the blobs it lacks (see docs/api-runner.md). Anything beyond the protocol's basic args is server-specific and rides the generic extra option, merged into the request body — the server you call defines and handles its own extension args (a client-supplied run id, where a server supports one, is such an extension; the request side never names it).
The mthds and mthds-agent CLIs send anonymous usage statistics, shared with every Pipelex and MTHDS tool: one event per command, saying which command ran, how it ended and how long it took, with the CLI's version, the operating system family and whether it ran in CI. They never send your methods, prompts, inputs, outputs, file names, pipe names or command arguments. The first mthds command that sends statistics prints a notice once; mthds-agent never prints it. Details are in docs/usage-statistics.md and at docs.pipelex.com/latest/setup/usage-statistics.
To turn them off, use any of these:
mthds telemetry disable # for this machine, for every Pipelex and MTHDS tool
export DO_NOT_TRACK=1 # the universal opt-out
export PIPELEX_USAGE_STATS=0 # every Pipelex and MTHDS tool, for one shell
mthds telemetry status says whether they are on and what keeps them off, mthds telemetry enable turns them back on, and mthds telemetry reset-id replaces the anonymous identifier. A DISABLE_TELEMETRY=1 written to ~/.mthds/config by an earlier version still holds.
Installing or publishing a method from a public GitHub repository also sends one event per method, carrying the package's manifest fields one by one (for a publish from a local directory, only the address, name and version mthds.sh read from GitHub), to rank methods on mthds.sh; DO_NOT_TRACK, DISABLE_TELEMETRY and mthds telemetry disable turn those off too. Publishing itself never depends on them.
make install # install dependencies
make check # typecheck + build
make test # run the test suite
make all # clean + check + test
make dev # watch mode
make run # build and run the CLI
make pack # create tarball for local npx testing
MIT
executestartvalidatepipeIotreeuploadBlob(blob, bytes) | the target the runner gave | Upload one missing blob with exactly the headers the runner named; throws BlobUploadError, which never carries the target's URL |