Skip to main content

Deterministic supervisor + per-slice runner machinery for an AFK, board-driven Claude implementation fleet (ADR-0007).

Project description

squadra

Deterministic supervisor + per-slice runner machinery for an AFK, board-driven Claude implementation fleet. squadra runs the implementation phase of an engineering pipeline (/tdd/qa) unattended across many vertical slices at once, with board work-item state as the single source of truth at every tier. squadra speaks a provider-neutral 3-bucket Lifecycle (queued / active / done); a BoardAccess adapter translates that to a concrete board's native semantics at the boundary. Azure DevOps (the az CLI) is the adapter that ships today; GitHub and GitLab are tracked backlog adapters the same contract-test suite will validate. Every fleet process is stateless or short-lived and reconstructs its view from the board on each run.

squadra is the packaged, reusable extraction of the fleet originally built in the gswa backend (design: ADR-0007 and its 2026-06-10 addendum, which lives in that repo; the provider-neutral seam is ADR-0001 and its design note). The package ships the deterministic machinery + its tests + scaffolding — the agent-side skills (/afk-slice-runner, /tdd, /qa, /cleanup-merged-branches) are consumer-owned and live in the consuming repo. squadra scaffolds genericized templates for the ones it drives (squadra init), then invokes them only by skill name through claude: scaffolding the template is not owning it, so the runtime boundary is unchanged.

squadra operates on a target repository, identified by FLEET_HOME (the current working directory by default). The installed package is never the working repo; the shell glue it drives ships as package data and is resolved via importlib.resources.

Install

squadra is built with uv + Hatchling (PEP 621, src layout). It has no third-party runtime dependencies (pure standard library); tmux, git, the board provider's CLI (the az CLI for the ADO adapter that ships today), and the claude CLI must be available on the host that runs the fleet.

# From a clone (development):
uv sync                      # create .venv, install squadra + dev tools

# As a dependency of another project (consumer):
#   editable path dep while iterating, or a git+HTTPS pin once stable
uv pip install -e /path/to/squadra

A single unified squadra CLI is installed (the API / composition root):

Command Role
squadra init [--provider P] [--check] scaffold an annotated squadra.toml; --check validates it against the live board
squadra tick [--dry-run] run one supervisor tick in-process
squadra {start|stop|status|log} hands-on ticker control (shells to the packaged fleetctl.sh)
squadra slice {init|update|heartbeat|show} the per-slice status-file ops (used by the runner wrapper)

python -m squadra.supervisor (one tick) and python -m squadra.status (the status-file CLI) remain as internal module entry points. The deprecated squadra-status console script has been dropped — use squadra slice instead.

Configuration

squadra reads a squadra.toml in the target repo (the what — which board, which states, which skills) and layers it under environment + flag overrides. The precedence, lowest to highest:

built-in defaults  <  squadra.toml  <  FLEET_* env  <  CLI flag

squadra.toml describes the target (like a kubeconfig context) and defaults the how wherever it can. Schema:

Section / key Default Meaning
[board].provider ado ado | github | gitlab. Selects the BoardAccess adapter (registry in the CLI composition root). ADO ships today; GitHub/GitLab are tracked backlog adapters.
[board].base_branch main The branch a slice PR must complete against for finalize-eligibility.
[board].tag_prefix fleet: Configurable namespace for the fleet's tags; detection is prefix-based (startswith). The five suffixes are fixed (see Tag vocabulary).
[board].parent_scope_ids [] (whole project) Optional claim-scope filter — only slices under these parents are claimable. Supersedes the legacy FLEET_EPIC_IDS env, which is still honored.
[board.states].queued / .active / .done Lists of the board's native state names mapped onto the three neutral Lifecycle buckets (many-native→one-neutral allowed). REQUIRED unless the provider is ADO-Basic, which defaults to ["To Do"] / ["Doing"] / ["Done"]. GitHub/GitLab statuses are user-defined, so they must be declared.
[pipeline].branch_template feat/slice-{id}-{slug} Slice branch naming. squadra owns the -a{attempt} retry suffix (fixed rule, not templated).
[pipeline].worktree_dir .claude/worktrees Where slice worktrees are created.
[pipeline].runner_skill /afk-slice-runner Skill the runner wrapper invokes per slice.
[pipeline].tdd_skill /tdd TDD skill name, threaded into the runner prompt.
[pipeline].qa_skill /qa QA skill name, threaded into the runner prompt.
[pipeline].cleanup_skill /cleanup-merged-branches Skill the finalize pass runs headlessly per merged branch.
[board]
provider         = "ado"          # ado | github | gitlab   (REQUIRED)
base_branch      = "main"
tag_prefix       = "fleet:"
parent_scope_ids = [105]          # optional; empty = whole project (was FLEET_EPIC_IDS)

[board.states]                    # REQUIRED unless provider is ADO-Basic; many-native→one allowed
queued = ["To Do"]
active = ["Doing"]
done   = ["Done"]

[pipeline]
branch_template = "feat/slice-{id}-{slug}"
worktree_dir    = ".claude/worktrees"
runner_skill    = "/afk-slice-runner"
tdd_skill       = "/tdd"
qa_skill        = "/qa"
cleanup_skill   = "/cleanup-merged-branches"

Operational and secret knobs stay env-only with the defaults in Constants: FLEET_MAX_RUNNERS, the intervals (FLEET_TICK_INTERVAL_SECONDS, FLEET_HEARTBEAT_INTERVAL_SECONDS, FLEET_STALENESS_THRESHOLD_SECONDS), FLEET_MAX_ATTEMPTS, FLEET_MODEL/FLEET_EFFORT, FLEET_HOME/FLEET_ROOT/FLEET_PYTHON, and the PAT. These are not in squadra.toml.

Safety is validate-against-board, not mandatory typing. validate_config() resolves the configured state names, tag prefix, and base branch against the live board — at startup of every tick and on squadra init --check — and fails loud on any mismatch (e.g. "configured active state 'Doing' not found among this project's states"). A typo can't silently strand or mis-claim slices.

Scaffolding (squadra init)

squadra init [--provider ado] makes adoption one command plus a few edits. It emits:

  • a complete, annotated squadra.toml — every key written with its default and provider taken from --provider; and
  • the genericized, consumer-owned runner-skill and cleanup-skill templates.

The skill templates are provider/repo-agnostic (a neutral lifecycle: claim-verify → worktree → seams → tdd → qa → park) with clearly-marked fill-in sections (e.g. ## Gates, shared-seam conventions) that work out of the box. squadra copies them out, then drives them only by skill name through claude — copying the template is not owning it, so the "machinery + tests + scaffolding" runtime boundary holds. squadra init --check runs validate_config() against the live board without writing anything.

The unified squadra init CLI surface lands with the PR2 core; the scaffolding engine itself ships on this branch.

Status file + heartbeat convention

Each in-flight slice has one status file — the micro view of its runner:

$FLEET_HOME/.claude/fleet/<issue-id>/status.json

The fleet root defaults under the target repo's .claude/fleet; on a bind-mounted checkout it survives container restart. Exclude it from the target repo's git (**/.claude/fleet/).

Schema

Field Type Meaning
issue_id int The slice's board work-item id
runner_id str Unique id of the runner attempt (e.g. runner-41-a1-…)
branch str Slice branch (feat/slice-<id>-<kebab>)
worktree str Absolute path of the slice's git worktree
pr_url str | null The slice's PR once opened
phase enum claimingseamstddqaparkeddone
parked_state enum | null needs-decision, qa-ready, awaiting-pr-approval, failed — non-null iff phase is parked
worker_roster list[str] Worker sub-agents the runner fanned out
started_at str ISO 8601 UTC, set at init
last_heartbeat str ISO 8601 UTC, stamped every heartbeat interval while the runner process is alive
attempt int 1-based attempt counter (transient failures retry up to 3)
last_error str | null Last recorded failure diagnostic

Concurrency contract

Two writers touch the file: the runner wrapper's deterministic heartbeat loop (liveness = process alive, independent of what the agent is doing) and the agent updating phase/parked_state/worker_roster at transitions. Every read-modify-write therefore happens under a sidecar flock (<issue-id>/.status.lock) and lands atomically via tmp-file + rename. Readers never see partial JSON; interleaved writers never lose fields.

CLI

Shell callers (the runner wrapper, skills) use the status CLI:

squadra slice init \
  --issue-id 41 --runner-id runner-41-a1 \
  --branch feat/slice-41-example --worktree "$FLEET_HOME/.claude/worktrees/feat+slice-41-example"

squadra slice update --issue-id 41 --phase tdd
squadra slice update --issue-id 41 \
  --phase parked --parked-state awaiting-pr-approval --pr-url <url>
squadra slice update --issue-id 41 --phase tdd --parked-state none
squadra slice heartbeat --issue-id 41
squadra slice show --issue-id 41

--fleet-root overrides the location (used by tests; defaults to $FLEET_HOME/.claude/fleet). python -m squadra.status … is equivalent.

Constants

All addendum constants live in squadra/constants.py and are env-tunable (read at process start — every fleet process is fresh per fire):

Constant Default Env override
Fleet home (target repo) current working directory FLEET_HOME
Fleet root $FLEET_HOME/.claude/fleet FLEET_ROOT
Interpreter for the fleet the supervisor's sys.executable (shell default python3) FLEET_PYTHON
Max parallel runners (the claim budget — 0 stops new claims only, it is not a safety lever) 2 FLEET_MAX_RUNNERS
Dry-run tick (plan + report, suppress every side effect) off FLEET_DRY_RUN
Heartbeat interval 60s FLEET_HEARTBEAT_INTERVAL_SECONDS
Staleness threshold 600s FLEET_STALENESS_THRESHOLD_SECONDS
Max attempts 3 FLEET_MAX_ATTEMPTS
Model claude-opus-4-8 FLEET_MODEL
Reasoning effort high FLEET_EFFORT

The compute tier (FLEET_MODEL / FLEET_EFFORT) is pinned in constants.py and applied as explicit claude --model … --effort … flags to every model-backed fleet call — the slice runner, the cleanup pass, and the auth probe (the probe pins --model only, since it does no reasoning). This is deliberate: a headless claude -p otherwise inherits whatever model an interactive session's settings.json happens to pin, so the fleet's tier would be an ambient side effect rather than a choice. Effort accepts the CLI levels low|medium|high|xhigh|max (model-dependent). To run hotter or cheaper, export FLEET_MODEL / FLEET_EFFORT — it flows through autostart → ticker → supervisor → runner panes by environment inheritance.

FLEET_PYTHON must point at an interpreter that has squadra installed; the supervisor injects its own sys.executable into each runner pane so the runner reaches squadra.* regardless of what python3 resolves to on PATH.

Tag vocabulary (parked sub-states are tags, not states — the ADO Basic process has only To Do / Doing / Done). The five suffixes — claimed, failed, needs-decision, qa-ready, awaiting-pr-approval — are fixed canonical vocabulary, carried under a configurable namespace prefix (default fleet:, set via [board].tag_prefix / FLEET_TAG_PREFIX), so the shipping defaults read fleet:claimed, fleet:failed, fleet:needs-decision, fleet:qa-ready, fleet:awaiting-pr-approval. Fleet-tag detection is prefix-based (startswith(prefix)), not a hardcoded literal set.

The neutral comment the fleet attaches at each transition is emitted by core as a structured event and rendered to the board's native markup at the adapter boundary (ADO → HTML, GitHub → Markdown) — core itself emits no markup.

Tuning path for the cap (addendum §1): raise as cores/headroom grow; back off on CPU saturation or 429s.

Slice runners

A runner is one short-lived, headless Claude session driving one slice. The supervisor launches each into its own per-slice ephemeral Docker compose project via SandboxAccess (ADR-0002 §5): the compose agent service's command is the deterministic wrapper (squadra/_scripts/runner-wrap.sh, resolved from the installed package and invoked as runner-wrap.sh <issue-id> <branch> [attempt]), so container lifecycle == agent lifecycle and docker inspect .State.ExitCode is the agent exit code.

The wrapper owns everything that must not depend on an LLM:

  • seeds status.json (init), records the runner.pid / pane-id / heartbeat.pid sidecars, and appends all output to <issue-id>/runner.log;
  • runs the heartbeat looplast_heartbeat advances every heartbeat interval for exactly as long as the wrapper process lives, so liveness means process alive, independent of how long the agent's current tool call runs;
  • invokes the headless session, threading the configured skill names into the prompt: claude -p "<runner_skill> issue-id=… branch=… attempt=… tdd-skill=… qa-skill=…" --dangerously-skip-permissions --model "$FLEET_MODEL" --effort "$FLEET_EFFORT". Because the runner/tdd/qa skill names are config (not hardcoded), the runner skill no longer hardcodes /tdd,/qa — it runs whatever names it is handed;
  • backstops an unexpected death: a healthy runner always exits parked (or done); if the session exits in any other phase, the wrapper stamps parked_state=failed + last_error and propagates the non-zero exit.

The afk-slice-runner skill (in the consuming repo) is the agent side of the contract: verify the claim, enter the slice worktree, write the shared seams before any fan-out, execute the configured tdd then qa skills unchanged, update phase/pr_url/worker_roster at transitions, park with the matching fleet tag + comment, exit. Parked states are never a hung session — they are queryable board state plus the status file.

Runner wrapper env knobs: FLEET_HOME, FLEET_ROOT, FLEET_HEARTBEAT_INTERVAL_SECONDS, FLEET_MODEL, FLEET_EFFORT, FLEET_RUNNER_SKILL, FLEET_TDD_SKILL, FLEET_QA_SKILL (the supervisor injects these into the pane env; when any is unset — e.g. a manual run — the wrapper resolves the default from squadra.config / squadra.constants, the single source of truth, the same fallback pattern as FLEET_MODEL/FLEET_EFFORT/the interval), FLEET_PYTHON, FLEET_CLAUDE_CMD (stubbed in the hermetic tests).

Supervisor

squadra/supervisor.py is the deterministic, token-free tick: no LLM anywhere, so it cannot hallucinate a board mutation, and it is unit-tested against in-memory fakes (including a divergent GitHub-shaped fake, which catches any hardcoded native-state leak and proves the core is provider-blind). It speaks the neutral Lifecycle throughout; the BoardAccess adapter maps to native states at the boundary. Each tick runs three ordered passes under one lock — finalize → reap → claim — so cap accounting is fresh before anything new launches (addendum §5). The native state names below (To Do/Doing/Done) are the ADO-Basic mapping of the neutral queued/active/done Lifecycle buckets; under another provider the adapter substitutes that board's configured names.

  1. Serialize — take a non-blocking flock on <fleet-root>/supervisor.lock; a tick that cannot get the lock exits 0 without touching the board.
  2. Count inflight — Issues in Doing carrying fleet:claimed. A human's manually-moved Doing Issue is invisible to the fleet (no tag): never counted, never reaped.
  3. Claim up to FLEET_MAX_RUNNERS − inflight available Issues, lowest id first. Available = To Do, no fleet:* tag, every Predecessor-linked Issue Done. Claim = To Do → Doing + tag fleet:claimed + a stamped comment, plus a local claimed-at marker for the watchdog. The branch is derived deterministically: feat/slice-<id>-<kebab-of-title> (suffix -aN on retries).
  4. Launch — one runner-wrap.sh <issue-id> <branch> <attempt> per claimed slice, as the agent-service command of its own per-slice ephemeral Docker compose project via SandboxAccess (build + compose up -d, non-blocking; docker compose logs against the slice project is the live view). A failed launch rolls the claim back (tag removed, Doing → To Do, comment), so no slice is stranded.

Only claim/launch depends on credentials beyond the board reads: a working ADO PAT (claiming a slice does host-side git remote ops — worktree create off origin/main, then push — over HTTPS+PAT, no SSH key) and a working claude (the contained runner is the fleet's single LLM call — finalize and reap are deterministic). A tick with claim work pending runs two preflights first, in order, and short-circuits on the first failure:

  1. PAT preflightgit ls-remote against the target remote (the live checkout's origin, else FLEET_APP_REPO_URL) using the env-var PAT credential helper, non-interactively (GIT_TERMINAL_PROMPT=0) with a 30s timeout. This exercises the exact auth path every host-side git op uses, so it cannot pass while a real claim's git op fails. A rejected/expired/wrong-scope PAT, an unreachable remote, a timeout, or no git all read as a failure.
  2. claude preflight — a throwaway claude -p 'reply READY' --dangerously-skip-permissions --model "$FLEET_MODEL" probe with a 120s hard timeout, passing only on exit 0 plus READY in stdout (dead auth, a transient API outage, and an unavailable model read identically).

On either failed probe the tick degrades to the reap pass only — every claim/launch decision is dropped (no slice is claimed, no To Do → Doing, no fleet:claimed), while in-flight finalize and reap still proceed — and retries next tick. The PAT failure logs one actionable line naming the fleet-ado-pat Key Vault secret and the rotation runbook (the consuming repo's docs/contributing/afk-fleet.mdKey Vault secrets & PAT rotation). The PAT probe runs first, so a dead PAT never pays to spawn claude. Idle, saturated, and finalize-only ticks never pay for either probe.

Finalize retires slices that are truly done: Issue Done and a completed PR for the slice branch. For each, it runs the consuming repo's /cleanup-merged-branches skill headlessly for that branch, drops every fleet:* tag, comments the PR link, and sets the status phase to done. A failed cleanup is retried next tick.

Reap (watchdog) recovers from dead runners, with two independent guards before it acts: stale (best liveness evidence older than the staleness threshold) and dead (the runner.pid sidecar process no longer exists). A stale-but-alive runner is always left alone. A failed park (parked_state=failed with no parked tag) is positive failure evidence and skips the staleness wait — reaped immediately once the pid is confirmed dead. Deliberately parked runners are never reaped. Reaping archives the dead worktree to .claude/fleet/<issue-id>/archive/attempt-N/, records the reap, drops fleet:claimed, and moves Doing → To Do; the next claim retries with attempt+1, and exhausted retries escalate to fleet:failed instead.

Dry runsquadra tick --dry-run (or FLEET_DRY_RUN=1, or python -m squadra.supervisor --dry-run) runs the same three passes' read+plan logic and reports the would-be actions, with every side effect suppressed at the TickSeams boundary (dry_run_seams): board writes become logged [dry-run] WOULD … no-ops, no runner pane launches, neither preflight probe runs (no git ls-remote for the PAT, no claude spawn for auth), no worktree is archived, no local status/marker file is written. Safety is the wrapped boundary, not a flag inside the passes, so a future pass cannot forget to honor it. Only the tick lock and the supervisor log are still written — coordination artifacts, not fleet state.

Scoping: [board].parent_scope_ids (a list of parent work-item ids, optional) restricts claiming to slices under those parents; it supersedes the legacy FLEET_EPIC_IDS env (comma-separated Epic ids), which is still honored. Empty (the default) means every unblocked queued work item in the project is eligible.

Host-side git hardening (sandbox-escape control)

The slice agent runs in a sandbox with the slice worktree bind-mounted as /work, and it commits there — so a prompt-injected or misbehaving agent can plant a git hook (pre-push, post-checkout, …) or set core.hooksPath to an agent-controlled directory inside the worktree. Host-side git ops later run against that same worktree in the supervisor context, which holds the ADO PAT and the VM managed identity. A hook firing there would be a sandbox escape into the credential-holding host.

squadra closes this by routing every host-side git invocation through squadra.git_host, which:

  • pins -c core.hooksPath=/dev/null on the argv (a command-line -c outranks any config the agent set, so neither a planted hook file nor an agent-set core.hooksPath can execute); and
  • on a checkout op, adds -c safe.directory=<that exact path> to clear git's dubious-ownership refusal when the op runs as a different OS user than the FLEET_HOME/worktree owner — scoped to the path, never safe.directory=* (a wildcard would trust every repo on the host and, paired with a planted hook, reopen the escape).

Both are transient command-line overrides — never written to .git/config, so the agent cannot strip them. The worktree create/archive/prune, branch delete, the base..HEAD commit count, and the app-repo bootstrap all inherit this automatically by going through the builder. The host-side push of a slice's commits — the credential-holding op the agent does not perform — is not yet wired in code (the deferred write-tail in the supervisor's _handoff); when it is, it must be built as host_git_argv(*credential_helper, "push", "origin", branch, work_dir=worktree), and tests/test_git_host.py already proves that contract neutralizes a planted pre-push hook (with a bare-push control), so a push that bypasses the builder is a reviewable regression rather than a silent escape.

Manual operator pushes against a fleet worktree must carry the same guard — run e.g. git -c core.hooksPath=/dev/null -c safe.directory="$PWD" push … (or export GIT_CONFIG_PARAMETERS) rather than a bare git push, so an operator's hands-on op cannot trip a planted hook either.

Activation (manual, opt-in)

Nothing starts the fleet automatically. Scope claiming with [board].parent_scope_ids (or the legacy FLEET_EPIC_IDS) before enabling. Two levers compose:

  • A dry run first (the safest first step): squadra tick --dry-run (or FLEET_DRY_RUN=1) runs the full finalize/reap/claim read+plan logic and logs every action a real tick WOULD take, but cannot mutate — board writes, runner launches, the preflight probes (the PAT git ls-remote and the claude auth spawn), and local fleet-state writes are all suppressed at the seams. Ticks log to $FLEET_ROOT/supervisor.log, so review the plan with squadra log.
  • One tick by hand: squadra tick — logs to $FLEET_ROOT/supervisor.log. Note that FLEET_MAX_RUNNERS=0 is not a read-only tick: it only zeroes the claim budget; finalize and reap still mutate the board (drop fleet tags, comment PR links, run the headless cleanup, move Doing → To Do). For a tick that cannot mutate, use --dry-run.
  • The fleet-host (production): systemd. The dedicated fleet-host VM schedules ticks with systemd, not tmux (ADR-0002 §11): a squadra.timer fires a oneshot squadra.service running squadra fleet-tick, which fetches the PAT + ANTHROPIC_API_KEY from Key Vault via the VM managed identity, syncs the app repo, and runs one tick. Render + install the units with squadra install-units --key-vault <kv> --fleet-home <checkout> [...]; this does not enable the timer. Provisioning, the on-host acceptance (goss), and activation (systemctl enable --now squadra.timer) live in docs/fleet-host/.
  • Local / dev on demand: tmux. squadra {start|stop|status|log} drives a detached fleet-ticker tmux session whose loop fires one tick every FLEET_TICK_INTERVAL_SECONDS (default 180): start is idempotent, stop kills it (in-flight runners in the separate fleet session keep going), status reports + tails, log tails (-f to follow). This is for hands-on local runs; the fleet-host uses systemd, above. (The boot-time fleet-autostart.sh / cron autostart paths have been retired in favor of systemd.)

Each fire is a fresh supervisor process under the same lock (the timer is only the schedule, so crash-only semantics are preserved). In-flight slice state is reconstructed from the board plus the bind-mounted .claude/fleet/ status files, so a re-started ticker resumes cleanly.

Watch the fleet: squadra status (is-it-running + recent log), squadra log -f (follow the supervisor log live), the board (fleet:* tags) is the macro view, per-slice status.json is the micro view, docker compose logs against a slice's compose project is the live agent view.

Development

uv sync                        # env + deps
uv run ruff check .            # lint
uv run ruff format --check .   # format
uv run pyright                 # strict type check
uv run pytest                  # unit + hermetic shell tests

The shell glue (runner-wrap.sh, fleet-tick.sh, fleetctl.sh) ships as package data under src/squadra/_scripts/. Anything that needs to invoke it — the supervisor's SandboxAccess launch, the squadra dispatcher, the tick entry point — resolves it via squadra._resources.resolve_script(...) (importlib.resources + chmod +x), never a path relative to FLEET_HOME. The fleet-host systemd unit templates ship the same way under src/squadra/_units/ (resolved via resolve_unit(...), rendered by squadra.units).

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

squadra-0.1.0a1.tar.gz (147.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

squadra-0.1.0a1-py3-none-any.whl (103.4 kB view details)

Uploaded Python 3

File details

Details for the file squadra-0.1.0a1.tar.gz.

File metadata

  • Download URL: squadra-0.1.0a1.tar.gz
  • Upload date:
  • Size: 147.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for squadra-0.1.0a1.tar.gz
Algorithm Hash digest
SHA256 96428bb277190d21ffcd976421614954312b56bfdd75620c79d1eb8304ba8114
MD5 d71958dd317f0dce376ffc8320837743
BLAKE2b-256 c54e960f4ad8c46600fd1d97ee3b2b51dc544b4f5b5389f0c6e2bc4beb93577c

See more details on using hashes here.

Provenance

The following attestation bundles were made for squadra-0.1.0a1.tar.gz:

Publisher: release.yml on rinman24/squadra

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file squadra-0.1.0a1-py3-none-any.whl.

File metadata

  • Download URL: squadra-0.1.0a1-py3-none-any.whl
  • Upload date:
  • Size: 103.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for squadra-0.1.0a1-py3-none-any.whl
Algorithm Hash digest
SHA256 47db2f64a92127b92dab0d92cf1f8c1ed2ff999a77c37ad759aa1b54939b5984
MD5 28f30ceefec0beca0067f8ec09588500
BLAKE2b-256 e1528270ecf3cdbdc54091de0e8a6911ba84183b6c324af33ab54d4952bb7cb7

See more details on using hashes here.

Provenance

The following attestation bundles were made for squadra-0.1.0a1-py3-none-any.whl:

Publisher: release.yml on rinman24/squadra

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page