Provider-pool-aware multi-agent coding orchestration.
Project description
Turma
Provider-pool-aware multi-agent coding orchestration with spec-driven planning, Beads task tracking, and resumable swarm execution.
Status
Early implementation phase, but the core workflow is functional end to end. Five CLI commands ship:
turma init— local project scaffolding.turma plan— the full author/critic loop with an explicit human approval gate and a resume CLI.turma plan-to-beads— transcribes an approved plan into a feature-tagged Beads task set.turma run— a single-feature sequential swarm orchestrator (preflight → reconcile → repair → main loop, one PR per Beads task).turma status— a read-only readout of a feature's Beads + PR + worktree state.
The swarm drives each task through a pluggable worker backend — claude-code
(default), codex, or opencode. A Gemini author backend exists, but its
consumer gemini CLI path is being retired by Google, so a Gemini worker
backend is deferred (see the Gemini CLI note in docs/architecture.md).
turma status, turma run, and turma plan each have a machine-readable
--json mode — status/plan as snapshots (turma.status.v1 /
turma.plan.v1), run as a turma.run.v1 NDJSON event stream — for scripts
and surfaces. Baseline CI and public architecture documentation round it out.
What It Is
Turma is designed as a two-phase workflow:
- Planning: generate and refine OpenSpec artifacts through an author/critic loop with explicit human approval.
- Execution: translate approved tasks into a Beads DAG and route work across multiple agent runtimes while tracking task and integration state.
The main design goal is to treat provider rate-limit pools as a routing input without overstating that pool independence alone solves throughput.
Repository Layout
.
├── .github/workflows/ # minimal CI
├── .agents/ # role guidance for author / critic / implementer / reviewer
├── .claude/commands/ # slash commands used in project context
├── openspec/ # feature specs and changes
├── docs/
│ ├── architecture.md # public system model
├── src/turma/ # Python package and CLI
├── tests/ # automated tests
├── CHANGELOG.md
├── LICENSE
├── README.md
├── turma.example.toml # committed config template
└── pyproject.toml
CLI Commands
Default development workflow:
uv sync
uv run turma --help
uv run turma init
uv run turma plan --feature oauth-auth # author/critic loop; suspends at the human gate
uv run turma plan --feature oauth-auth --resume --approve # approve: writes the APPROVED marker
uv run turma plan-to-beads --feature oauth-auth # transcribe the approved plan into Beads tasks
uv run turma run --feature oauth-auth # orchestrate execution against the Beads DAG
uv run turma status --feature oauth-auth
turma run preflight refuses to start until the feature has an APPROVED
marker (from the plan human gate) and a TRANSCRIBED.md (from
turma plan-to-beads) — see the Resume CLI and Plan-to-Beads sections below.
Current command status:
turma initis implementedturma planruns the full author/critic loop with a human approval gate and a resume CLIturma plan-to-beadstranscribes an approved plan into a feature-tagged Beads task set (requiresbdand Dolt; see Plan-to-Beads below)turma rundrives a single-feature sequential swarm against the transcribed Beads DAG (see Swarm Execution below)turma status --feature <name>prints a read-only readout of a feature's current Beads + PR + worktree state (see Feature Status below)
turma init expects turma.example.toml to exist in the target directory. It
creates turma.toml from that template and updates .gitignore with
Turma-managed entries.
turma plan --feature <name> does the following per round:
- reads
planning.author_modelandplanning.critic_modelfromturma.toml - requires
.agents/author.mdand.agents/critic.md - on round 1, scaffolds an OpenSpec change with
openspecand generatesproposal.md,design.md, andtasks.md - on round ≥ 2, runs the two-call revision: author first writes
response_{N-1}.mdreplying to each finding incritique_{N-1}.md, then regenerates the three artifacts using that response as context - runs the critic backend to produce a strict
critique_N.md - routes on the critic's
## Status:token:blocking→ revise,nits_only/approved→ await human, malformed →needs_human_review - suspends at
awaiting_human_approvalwith the exact resume commands printed
Loop budget: planning.max_rounds caps the iterations; repeated unresolved
blocking finding IDs across two rounds also route to needs_human_review.
Filesystem terminal markers (APPROVED, ABANDONED.md,
NEEDS_HUMAN_REVIEW.md) are authoritative — re-running turma plan on an
already-terminal plan is a read-only no-op.
Planning quality depends on the chosen backend/model. Claude-backed planning
is currently the strongest validated path. OpenCode transport is validated,
but provider/model quality varies. Gemini requires the gemini CLI
(npm install -g @google/gemini-cli) — but Google is retiring that CLI: the
consumer free / Pro / Ultra tiers stopped serving requests on 2026-06-18,
and it continues only via paid Gemini / Gemini Enterprise Agent Platform API
keys, Standard/Enterprise Gemini Code Assist licenses, or Gemini Code Assist
for GitHub through Google Cloud. The replacement Antigravity CLI is not a
drop-in, so Gemini authoring is supported only on those paid/enterprise paths
pending that transition
(announcement).
turma plan only produces and critiques spec artifacts; it does not commit
changes or orchestrate execution. Transcription is handled by
turma plan-to-beads and execution by turma run (see below).
Resume CLI
uv run turma plan --feature <name> --resume # read-only status
uv run turma plan --feature <name> --resume --approve # write APPROVED
uv run turma plan --feature <name> --resume --revise "<why>" # advance into a new round
uv run turma plan --feature <name> --resume --abandon "<why>" # write ABANDONED.md
uv run turma plan --feature <name> --resume --approve --override "<why>" # override from needs_human_review
--approve, --revise, and --abandon are valid only when the graph is
suspended at awaiting_human_approval. --approve --override is valid only
when the graph has halted in needs_human_review.
Machine-readable output
turma plan --feature <name> --json (and any --resume variant with --json)
emits a single turma.plan.v1 JSON snapshot of the planning outcome instead
of text — for scripts and surfaces. plan runs to one terminal-or-suspended
outcome per invocation, so this is a snapshot (like status --json), not an
event stream:
{
"schema": "turma.plan.v1",
"feature": "oauth",
"state": "awaiting_human_approval",
"round": 2,
"next_nodes": ["awaiting_human_approval"],
"checkpoint": ".langgraph/oauth.sqlite",
"artifacts_dir": "openspec/changes/oauth/",
"action": null
}
state is one of awaiting_human_approval / approved / needs_revision /
abandoned / needs_human_review; action is the resume action
(approve / revise / abandon / override_approve / status) for --resume
invocations, null for a fresh plan. stdout is exactly one JSON document —
all per-round progress is suppressed in --json mode. On failure, a single
{"schema": "turma.plan.v1", "error": "<message>"} object, exit nonzero. Absent
the flag, text output is unchanged.
Together with status --json (snapshot) and run --json (NDJSON stream), this
completes the machine-readable operator surface.
Plan-to-Beads
Once a plan has an APPROVED marker, turma plan-to-beads translates its
tasks.md into a feature-tagged set of Beads tasks with typed entries,
priorities, and dependency edges.
uv run turma plan-to-beads --feature <name>
uv run turma plan-to-beads --feature <name> --force
Prerequisites
bd (Beads) is a Go binary, not a PyPI package. Install it together with
Dolt (Beads' storage backend):
brew install beads # pulls Dolt and other required dependencies
turma plan-to-beads raises a clear error with the brew install beads
hint when bd is not on PATH. See
https://github.com/steveyegge/beads for non-macOS install paths.
Behavior
- Gates on the
APPROVEDterminal marker; a plan that is not approved is rejected. - Parses
tasks.mdvia the strict parser (seeopenspec/changes/beads-transcription/design.mdfor the grammar). - Translates parser task types to Beads-native types
(
impl/test→task,docs→chore,spec→decision) and parser priority to Beads priority (min(section_number - 1, 4); 0 is highest). - Creates each section's Beads task with a
feature:<name>label for downstream filtering, then addsbd dep addblocking edges from each section to itsblocked-bypredecessors. - On full success writes
TRANSCRIBED.mdin the change directory recording the created Beads task ids in section order. - Prints a compact summary of the created tasks on stdout.
--force semantics
- With a prior
TRANSCRIBED.mdpresent: closes the recorded Beads task ids in reverse section order, removes the marker, and re-runs the pipeline. - With no marker but feature-tagged Beads orphans present (from a
prior failed attempt): closes the orphans via
bd list --label feature:<name>and re-runs the pipeline. - With neither a marker nor orphans:
--forceis a no-op and the pipeline runs normally. - A
TRANSCRIBED.mdthat exists but parses to no- section N: <id>lines is hard-rejected under--forceto avoid duplicate creation against a corrupt marker. Inspect or delete the file manually and retry.
Partial-failure recovery
Turma does not roll back partial Beads state on failure. If an adapter
call fails mid-run, the already-created tasks remain on the Beads side
with their feature:<name> label, and no TRANSCRIBED.md is written.
A re-run without --force detects the feature-tagged orphans during
preflight and surfaces their ids plus two recovery paths:
# Option A — manual close, then retry from scratch
bd close <id> <id> ...
uv run turma plan-to-beads --feature <name>
# Option B — let --force close the orphans for you
uv run turma plan-to-beads --feature <name> --force
Validation commands:
uv run turma --help
uv run python -m turma --help
uv run pytest
Swarm Execution
Once a feature has been transcribed to Beads, turma run drives a
single-feature sequential execution loop that claims ready Beads
tasks, runs a worker agent inside a per-task git worktree, opens one
PR per completed task against the default base branch (main), and
stops. Review, merge, and release are human-driven.
uv run turma run --feature <name>
uv run turma run --feature <name> --max-tasks 1 # smoke one task end-to-end
uv run turma run --feature <name> --backend claude-code
uv run turma run --feature <name> --backend codex # drive Codex instead of Claude Code
uv run turma run --feature <name> --backend opencode # drive OpenCode
uv run turma run --feature <name> --dry-run # preflight + reconcile only
Worker backends are selectable via --backend (or [swarm] worker_backend); registered names are claude-code (default), codex,
and opencode. The Codex worker drives codex exec in workspace-write
mode; the OpenCode worker drives opencode run --dangerously-skip-permissions.
All backends honor the same sentinel completion contract as Claude Code and
share one subprocess/timeout/sentinel path, differing only in argv.
Machine-readable output
turma run --feature <name> --json emits a turma.run.v1 NDJSON event
stream — one compact JSON object per line, flushed as each transition
happens — instead of the text lines, for scripts and live surfaces (a VS Code
extension, an MCP client, a dashboard):
uv run turma run --feature oauth-auth --json | while read -r line; do
echo "$line" | jq -r '"\(.event) \(.task_id // "")"'
done
Every line is {"schema": "turma.run.v1", "event": "<name>", ...}. Events map
1:1 to the text lines — fetch_advanced, reconcile_summary, task_claimed,
worktree_setup, worker_running, commit, push, task_opened,
task_failed, merge_advancement, done, and so on. On failure the run still
exits nonzero; events emitted before the failure remain valid records, and the
terminal error is a final {"event": "error", "message": ...} line (in
--json mode) rather than the error: <msg> text. Absent the flag, the text
output is unchanged.
Config: turma run reads the [swarm] block from turma.toml
for worker_backend, worker_timeout, max_retries,
worktree_root, and base_branch. CLI flags take precedence —
--backend overrides worker_backend, and --max-tasks is a
per-invocation cap with no config equivalent. Missing or partial
[swarm] blocks fall back to the defaults in
turma.example.toml.
Prerequisites
bd(Beads) on PATH (brew install beads; see Plan-to-Beads above)giton PATHgh(GitHub CLI) on PATH with an authenticated session (gh auth loginonce; verified at startup viagh auth status)- the selected worker backend's CLI on PATH:
claude(defaultclaude-code),codex(--backend codex), oropencode(--backend opencode). Only the selected backend's CLI is required;--dry-runrequires none because the worker is never invoked. - A transcribed feature:
openspec/changes/<name>/APPROVEDandopenspec/changes/<name>/TRANSCRIBED.mdmust both exist. Missing either halts with a pointer back toturma planorturma plan-to-beads.
Base-branch sync
Every non---dry-run invocation begins by fast-forwarding the
local <base_branch> to origin — after preflight, before
reconciliation. It is a three-call sequence: a git symbolic-ref --short HEAD precheck (refuses cleanly unless <base_branch> is
the checked-out branch), then git fetch origin <base_branch>,
then git merge --ff-only origin/<base_branch>. Divergent local
history is refused rather than force-reconciled. Without this,
chained features stall: when task A's PR merges between runs and
the next turma run claims the dependent task B, B's worktree
would otherwise be cut from a stale local base that lacks A's
commits, and the worker would refuse to operate against the
missing precondition.
The merge --ff-only step refuses to overwrite divergent local
history — if local <base_branch> has commits that origin
doesn't, the run halts with a typed PlanningError naming the
branch and pointing at both triage commands, git log <base_branch>..origin/<base_branch> and git log origin/<base_branch>..<base_branch>, to compare directions.
Operators triage manually rather than letting turma rebase or
merge automatically.
--dry-run skips the fetch (a fast-forward mutates the local
ref) and prints fetch: skipped (--dry-run) so the omission
is explicit.
The one-feature loop
turma run's top-level state machine:
preflight → fetch_and_ff_base → reconcile → repair → merge_advancement → main_loop
main_loop runs, per ready Beads task:
claim → setup_worktree → run_worker → (sentinel) → commit → push → open_pr → mark_pr_open
mark_pr_open records the PR number on the Beads task via a
turma-pr:<N> label and leaves the task in in_progress with
its worktree on disk. The matching close_task +
cleanup_worktree defer to the next turma run's merge
advancement sweep (see below), which fires only after GitHub
reports the PR as merged. This is what keeps dependents from
being claimed against an unmerged base.
Failed steps enter the retry path via fail_task on the Beads task.
A worker that claims success but leaves the worktree clean
(.task_complete present but git status --porcelain empty) is
treated as a failure with a canned reason so a non-editing worker
cannot land an empty commit.
The worker signals completion via filesystem sentinels inside the worktree:
.task_complete— worker believes the task is done; orchestrator commits, pushes, opens a PR, and labels the Beads task withturma-pr:<N>. Close + cleanup defer to merge advancement..task_failed— worker hit an unresolvable blocker; contents are the failure reason. Orchestrator callsfail_taskand leaves the worktree on disk for triage.- No sentinel after worker exit → failure with reason
"worker exited without writing a completion marker".
Retry budget and halt conditions
Retry state lives on the Beads task:
turma-retries:<n>label — attempt counter, absent means zero.needs_human_reviewlabel — added on budget exhaustion solist_ready_tasksfilters the task out of future listings.
On failure, the orchestrator reads retries_so_far and calls
fail_task(reason, retries_so_far, max_retries). Budget remaining
→ the task returns to open for a future re-attempt. Budget
exhausted → the orchestrator halts the whole run so the operator
can triage via bd list --label needs_human_review.
max_tasks caps the outer loop at N successfully-claimed tasks
(claim races do not consume budget). Default is unbounded.
Reconciliation on resume
Reconciliation always runs at startup — including --dry-run —
before the main loop. It walks the Beads in_progress set and
classifies each task into one of six finding types based on the
worktree filesystem and GitHub PR state:
| Finding | Cause | Repair |
|---|---|---|
missing-worktree |
Beads says in_progress, worktree absent | release the claim (counts against the retry budget) |
completion-pending |
.task_complete present, no open PR |
commit + push + open_pr + mark_pr_open |
completion-pending-with-pr |
.task_complete present, PR already open |
mark_pr_open (no new PR; close + cleanup defer to merge advancement) |
failure-pending |
.task_failed present |
fail_task with the worker's reason (worktree left for triage) |
stale-no-sentinels |
worktree + branch exist, no sentinel | halt before the main loop; operator decides |
orphan-branch |
task/<feature>/* branch with no in_progress task |
log only; operator triage |
Reconciliation itself is read-only: every mutation (fail_task,
close_task, commit, push, gh pr create) is performed by the
repair phase in the main loop, and --dry-run skips the repair
phase entirely.
Merge advancement
Between the repair phase and the main loop, turma run sweeps
every in_progress task that carries a turma-pr:<N> label and
queries the PR's GitHub state via gh pr view <N> --json state.
The dispatch is read-only-then-mutate — one gh read per
labelled task, then exactly one of:
gh returns |
Action |
|---|---|
MERGED |
unmark_pr_open + close_task + cleanup_worktree — dependents become claimable on the same turma run |
OPEN |
leave alone (drafts return OPEN from --json state and fall through this branch unchanged) |
CLOSED without merge |
fail_task with reason "PR #<N> closed without merge" so the retry budget applies |
| PR not found / 404 | halt with PlanningError; the label is stale and the operator triages |
Tasks without a turma-pr:<N> label are skipped (no gh call),
matching the label-gated dispatch. --dry-run performs the
PR-state reads but no mutations.
The sweep prints one line per processed task, prefixed with
merge-advancement: so the source is unambiguous in the run
log.
Failure modes (CLI)
error: <msg> starts with |
Cause |
|---|---|
feature 'X' is not APPROVED |
no APPROVED marker; run turma plan |
feature 'X' has not been transcribed |
no TRANSCRIBED.md; run turma plan-to-beads |
bd CLI not found |
bd missing from PATH |
gh CLI not found |
gh missing from PATH |
gh session not authenticated |
run gh auth login |
stale worktree for <id> has no sentinels |
reconcile caught ambiguous state; operator decides |
retry budget exhausted on <id> |
task hit max_retries; triage with bd list --label needs_human_review |
Worked example
Against a feature already transcribed to Beads (see Plan-to-Beads above):
# First run — opens a PR for task 1. The Beads task stays
# in_progress with a `turma-pr:<N>` label; the worktree at
# .worktrees/oauth-auth/<bd-id>/ stays on disk awaiting merge.
# A dependent task is NOT yet claimable.
uv run turma run --feature oauth-auth --max-tasks 1
# Reviewer merges the PR on GitHub (or via `gh pr merge <N>`).
# Nothing local needs to change — the merge is the only signal
# the orchestrator depends on.
# Second run — the merge advancement sweep observes MERGED,
# unmarks the label, closes the task, and removes the
# worktree. The dependent task is now ready and gets claimed
# in the same invocation.
uv run turma run --feature oauth-auth
# Operator triage after budget exhaustion.
bd list --label needs_human_review
bd show <id>
A detailed end-to-end smoke procedure against real bd + gh +
claude lives in docs/smoke-turma-run.md.
Feature status
turma status --feature <name> prints a read-only readout of the
feature's current Beads + GitHub PR + worktree state. The
command never mutates anything — no claim_task, no
close_task, no fail_task, no commits, no pushes, no
gh pr create. Useful between turma run invocations to
answer "where is this feature right now?"
uv run turma status --feature oauth-auth
The readout has six sections, in fixed order, each with a
(none) placeholder when empty:
- feature header — spec dir presence,
APPROVED/TRANSCRIBED.mdflags, with next-step hints (e.g. "runturma plan --feature ...first") inline when missing. A missing spec dir does not raise; the readout still prints. - task counters —
ready/in_progress/blocked / deferred/closed/needs_human_review. Buckets are mutually exclusive: a task with theneeds_human_reviewlabel is counted there regardless of its bd status;opentasks not inbd ready's view are counted as dependency-blocked. - ready tasks — claimable right now.
- in-progress tasks — per-task:
retries: <n> / <max>, worktree presence, sentinel state (complete | failed: "<reason>" | none). The.task_failedbody is truncated to the first line in the readout; the full file stays on disk for triage. When the task carries aturma-pr:<N>label (recorded by the success path / repair phase), an extrapr: #<N> (<state>) <url>line is added below the sentinel — state and URL come from a livegh pr view, not the cached label, so MERGED PRs awaiting the nextturma runsweep are visible here. - pull requests — every PR for
task/<feature>/*head branches across all states (OPEN/MERGED/CLOSED/DRAFT). - orphan branches — local task branches whose Beads task
isn't
in_progress. Matchesreconcile_feature's classification exactly; ready-task retry branches will appear here until the nextturma runre-claims them.
Worked example against a feature mid-flight (synthetic; absolute paths and PR URLs depend on your repo):
$ uv run turma status --feature oauth-auth
feature: oauth-auth
spec: openspec/changes/oauth-auth/
approved: yes
transcribed: yes
tasks:
ready: 2
in_progress: 1
blocked / deferred: 1
closed: 3
needs_human_review: 0
ready tasks:
bd-oauth-4 — Wire token refresh
bd-oauth-5 — Add session expiry tests
in-progress tasks:
bd-oauth-3 — Persist sessions in Redis
retries: 0 / 1
worktree: <repo>/.worktrees/oauth-auth/bd-oauth-3/ (present)
sentinel: failed: "redis client connection refused"
pull requests:
#14 OPEN — [impl] Token issuance endpoint
head: task/oauth-auth/bd-oauth-1
url: https://github.com/your-org/your-repo/pull/14
#13 MERGED — [impl] Add OAuth provider config
head: task/oauth-auth/bd-oauth-2
url: https://github.com/your-org/your-repo/pull/13
orphan branches:
(none)
Reading it: 3 closed (PR #13 already merged) + 1 in-progress
(bd-oauth-3's worker wrote .task_failed with the first-line
reason rendered inline; the full body is still on disk under the
worktree for triage) + 2 ready + 1 dependency-blocked (counted in
blocked / deferred). One PR is still open against the
in-flight task; no branches without a corresponding active task,
so orphan branches is (none).
Adapter failures (bd list non-zero exit, gh pr list
non-zero exit, etc.) raise PlanningError and exit 1 with
error: <msg> on stdout — no partial readout printed.
Machine-readable output
turma status --feature <name> --json emits the same state as a
pretty-printed (indent=2) JSON object instead of text, for scripts
and dashboards:
uv run turma status --feature oauth-auth --json | jq '.tasks'
The payload carries a stable top-level "schema": "turma.status.v1"
and mirrors the text sections 1:1: feature, spec, tasks
(counters), ready, in_progress, pull_requests, orphan_branches.
Each in_progress entry has worktree { present, path }, a structured
sentinel { status, reason } (status ∈ complete / failed /
null), and pr { number, state, url } (or null when the task
carries no turma-pr: label). The same read-only / no-mutation
invariant applies, and --json never emits a partial document. On
failure it emits a single structured error object —
{"schema": "turma.status.v1", "error": "<message>"}, exit 1 — so the
surface stays parseable (matching plan --json; text mode still prints
error: <msg>).
Core Docs
Next Implementation Steps
- post-merge advancement: detect when a
turma run-opened PR has been merged and unblock dependent Beads tasks automatically - parallel task execution + per-task backend routing (
worker-backend:<id>labels) - Gemini worker implementation — deferred pending Google's Gemini CLI → Antigravity CLI transition (Codex and OpenCode are shipped)
- a
turma run --clean <feature>flag to bulk-remove failed worktrees and branches
License
MIT
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file turma-0.4.0.tar.gz.
File metadata
- Download URL: turma-0.4.0.tar.gz
- Upload date:
- Size: 181.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.2 {"installer":{"name":"uv","version":"0.10.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
46f66eaad3c7a77df04fdf6489859457698a4c8a5d945fbf324b4a6c8944d24f
|
|
| MD5 |
5ed78714c08dd4079f7fed640989a771
|
|
| BLAKE2b-256 |
caa516746428a82ad594cf9e41787c1b3df001f1236c10aab2f1e736202ac2ff
|
File details
Details for the file turma-0.4.0-py3-none-any.whl.
File metadata
- Download URL: turma-0.4.0-py3-none-any.whl
- Upload date:
- Size: 92.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.2 {"installer":{"name":"uv","version":"0.10.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8a17ac19c8ff36d54af6baab8313e8878e0d60da772e7c5cd0df2b1c4d058a42
|
|
| MD5 |
4d1a2edf834254beb277a14285caf2a1
|
|
| BLAKE2b-256 |
2f5e984bf56b532c882af54c38e3e7839d9dc4b51eec33dc037f42ab3eccda8e
|