Skip to content

layout

strands_teams.layout

Layout descriptor — single source of truth for every artifact path.

TeamsLayout computes every path a run touches (teams dir, per-team findings.md / metadata.json, the team-debug JSONL, the artifact-output dir, per-area artifact filenames, the executive-summary filename, and the planning-context sentinel) from a single output-directory root plus a use-case-supplied naming scheme.

Replaces the scattered BUNDLE.{area}.md / CONTEXT.md / heuristic_summary.json / findings.md string literals that the pre-split team-tools module and hooks.py duplicated across dispatch, read, write, and quality functions (explore.yaml hidden couplings #4, #10; AC-2-1).

Frozen dataclass so it is trivially picklable and safe to place into invocation_state (research-strands.yaml §ToolContext).

TeamsLayout dataclass

TeamsLayout(
    output_root,
    artifact_prefix="BUNDLE",
    artifact_suffix=".md",
    artifacts_subdir="bundles",
    teams_subdir="teams",
    scratch_subdir="tmp",
    findings_name="findings.md",
    findings_json_name="findings.json",
    contract_name="contract.json",
    metadata_name="metadata.json",
    debug_log_name="team_debug.jsonl",
    event_log_name="events.jsonl",
    executive_summary_name="CONTEXT.md",
    planning_sentinel_name="heuristic_summary.json",
    revisions_subdir="revisions",
    alias_name="aliases.json",
    approvals_subdir="approvals",
    human_gates_subdir="human_gates",
    decision_log_name="decisions.jsonl",
)

Owns all artifact paths for a run, derived from one output root.

Every path-producing function in dispatch/read/write/quality code must call a method here rather than re-deriving a literal, so the naming scheme lives in exactly one place.

Attributes:

Name Type Description
output_root Path

The run's output-directory root (e.g., <repo>/.code-context).

artifact_prefix str

Prefix for per-area artifact files (BUNDLE -> BUNDLE.{area}.md).

artifact_suffix str

Extension for artifact files (default .md).

artifacts_subdir str

Subdirectory under the root holding per-area artifacts.

teams_subdir str

Subdirectory (under a scratch dir) holding per-team output.

scratch_subdir str

Scratch directory under the root for transient team data.

findings_name str

Per-team human findings filename.

findings_json_name str

Per-team typed-findings sidecar filename.

contract_name str

Per-team task-contract sidecar filename (authority, budget, acceptance criteria, input revisions).

metadata_name str

Per-team metadata filename.

debug_log_name str

Team-dispatch debug JSONL filename.

event_log_name str

Typed append-only event-plane JSONL filename (Phase 5 / spec 006), co-located with the team-debug log under the teams dir.

executive_summary_name str

Filename for the top-level executive summary.

planning_sentinel_name str

Filename for the planning-context sentinel produced by a deterministic pre-run step.

revisions_subdir str

Subdirectory under the root holding the content-addressed immutable revision store (per-revision_id records).

alias_name str

Filename of the mutable logical_name -> current revision_id alias pointer map (Phase 4 / spec 005), resolved under output_root the same way :meth:planning_sentinel_path is.

approvals_subdir str

Subdirectory (under the scratch dir) holding the per-token_id :class:~strands_teams.models.ApprovalToken store (Phase 6 / spec 007). The single source of every approval path — the PolicyEngine reads it, never a duplicated literal (AC-3-7).

human_gates_subdir str

Subdirectory (under the scratch dir) holding pending :class:~strands_teams.models.HumanDecision gate sentinels the runner suspends on and resumes from (Phase 6 / spec 007; AC-4-3, AC-4-5).

decision_log_name str

Filename of the append-only decision log — one JSON-encoded :class:~strands_teams.models.Decision per line — written on every :class:~strands_teams.policy.PolicyEngine verdict (AC-2-4).

teams_dir

teams_dir()

Directory holding every team's output subdirectory.

Source code in packages/strands-teams/src/strands_teams/layout.py
def teams_dir(self) -> Path:
    """Directory holding every team's output subdirectory."""
    return self.output_root / self.scratch_subdir / self.teams_subdir

team_dir

team_dir(team_id)

Directory for a single team's output.

Source code in packages/strands-teams/src/strands_teams/layout.py
def team_dir(self, team_id: str) -> Path:
    """Directory for a single team's output."""
    return self.teams_dir() / team_id

findings_path

findings_path(team_id)

Path to a team's findings.md.

Source code in packages/strands-teams/src/strands_teams/layout.py
def findings_path(self, team_id: str) -> Path:
    """Path to a team's ``findings.md``."""
    return self.team_dir(team_id) / self.findings_name

findings_json_path

findings_json_path(team_id)

Path to a team's typed findings.json sidecar.

Resolves under the same per-team directory as :meth:findings_path, so the human markdown and the machine-readable typed findings live side by side (AC-2-2).

Source code in packages/strands-teams/src/strands_teams/layout.py
def findings_json_path(self, team_id: str) -> Path:
    """Path to a team's typed ``findings.json`` sidecar.

    Resolves under the same per-team directory as :meth:`findings_path`, so
    the human markdown and the machine-readable typed findings live side by
    side (AC-2-2).
    """
    return self.team_dir(team_id) / self.findings_json_name

contract_path

contract_path(team_id)

Path to a team's task-contract sidecar (contract.json).

Resolves under the same per-team directory as :meth:findings_path, so a team's granted authority, budget, acceptance criteria, and input revisions are persisted next to its findings and metadata (AC-2-6). The :class:~strands_teams.hooks.ArtifactQualityHook reads every team's acceptance_criteria from these files to evaluate criteria coverage.

Source code in packages/strands-teams/src/strands_teams/layout.py
def contract_path(self, team_id: str) -> Path:
    """Path to a team's task-contract sidecar (``contract.json``).

    Resolves under the same per-team directory as :meth:`findings_path`, so a
    team's granted authority, budget, acceptance criteria, and input
    revisions are persisted next to its findings and metadata (AC-2-6). The
    :class:`~strands_teams.hooks.ArtifactQualityHook` reads every team's
    ``acceptance_criteria`` from these files to evaluate criteria coverage.
    """
    return self.team_dir(team_id) / self.contract_name

contract_glob

contract_glob()

Glob (relative to :meth:teams_dir) matching every per-team contract file.

Source code in packages/strands-teams/src/strands_teams/layout.py
def contract_glob(self) -> str:
    """Glob (relative to :meth:`teams_dir`) matching every per-team contract file."""
    return f"*/{self.contract_name}"

metadata_path

metadata_path(team_id)

Path to a team's metadata.json.

Source code in packages/strands-teams/src/strands_teams/layout.py
def metadata_path(self, team_id: str) -> Path:
    """Path to a team's ``metadata.json``."""
    return self.team_dir(team_id) / self.metadata_name

debug_log_path

debug_log_path()

Path to the append-only team-dispatch debug JSONL.

Source code in packages/strands-teams/src/strands_teams/layout.py
def debug_log_path(self) -> Path:
    """Path to the append-only team-dispatch debug JSONL."""
    return self.teams_dir() / self.debug_log_name

event_log_path

event_log_path()

Path to the typed append-only event-plane JSONL (Phase 5 / spec 006).

Co-located with :meth:debug_log_path under the teams dir so the typed event plane supersedes the untyped team_debug.jsonl without moving the run's scratch layout (AC-2-1). One JSON-encoded :class:~strands_teams.models.TeamEvent per line.

Source code in packages/strands-teams/src/strands_teams/layout.py
def event_log_path(self) -> Path:
    """Path to the typed append-only event-plane JSONL (Phase 5 / spec 006).

    Co-located with :meth:`debug_log_path` under the teams dir so the typed
    event plane supersedes the untyped ``team_debug.jsonl`` without moving the
    run's scratch layout (AC-2-1). One JSON-encoded
    :class:`~strands_teams.models.TeamEvent` per line.
    """
    return self.teams_dir() / self.event_log_name

artifacts_dir

artifacts_dir()

Directory holding per-area artifact files.

Source code in packages/strands-teams/src/strands_teams/layout.py
def artifacts_dir(self) -> Path:
    """Directory holding per-area artifact files."""
    return self.output_root / self.artifacts_subdir

artifact_path

artifact_path(area)

Path to a per-area artifact file (e.g., bundles/BUNDLE.auth.md).

Source code in packages/strands-teams/src/strands_teams/layout.py
def artifact_path(self, area: str) -> Path:
    """Path to a per-area artifact file (e.g., ``bundles/BUNDLE.auth.md``)."""
    return self.artifacts_dir() / f"{self.artifact_prefix}.{area}{self.artifact_suffix}"

artifact_glob

artifact_glob()

Glob pattern matching every per-area artifact file.

Source code in packages/strands-teams/src/strands_teams/layout.py
def artifact_glob(self) -> str:
    """Glob pattern matching every per-area artifact file."""
    return f"{self.artifact_prefix}.*{self.artifact_suffix}"

area_from_artifact

area_from_artifact(path)

Extract the area identifier from an artifact file path.

Inverse of :meth:artifact_path — strips the prefix/suffix so the quality hook can name a weak artifact by its area.

Source code in packages/strands-teams/src/strands_teams/layout.py
def area_from_artifact(self, path: Path) -> str:
    """Extract the area identifier from an artifact file path.

    Inverse of :meth:`artifact_path` — strips the prefix/suffix so the
    quality hook can name a weak artifact by its area.
    """
    return path.name.removeprefix(f"{self.artifact_prefix}.").removesuffix(self.artifact_suffix)

executive_summary_path

executive_summary_path()

Path to the top-level executive summary file.

Source code in packages/strands-teams/src/strands_teams/layout.py
def executive_summary_path(self) -> Path:
    """Path to the top-level executive summary file."""
    return self.output_root / self.executive_summary_name

planning_sentinel_path

planning_sentinel_path()

Path to the planning-context sentinel produced by the pre-run step.

Source code in packages/strands-teams/src/strands_teams/layout.py
def planning_sentinel_path(self) -> Path:
    """Path to the planning-context sentinel produced by the pre-run step."""
    return self.output_root / self.planning_sentinel_name

revisions_dir

revisions_dir()

Directory holding the content-addressed immutable revision store (AC-1-3).

Source code in packages/strands-teams/src/strands_teams/layout.py
def revisions_dir(self) -> Path:
    """Directory holding the content-addressed immutable revision store (AC-1-3)."""
    return self.output_root / self.revisions_subdir

revision_path

revision_path(logical_name, revision_id)

Resolve the on-disk path for one (logical_name, revision_id) revision (AC-1-3).

Every revision of a logical artifact is grouped under a per-artifact subdirectory of :meth:revisions_dir, keyed by revision_id (which is content-addressed). This is the ONLY place a revision path is derived, so dispatch/read/write/promotion code never duplicates the literal (AC-1-3).

Source code in packages/strands-teams/src/strands_teams/layout.py
def revision_path(self, logical_name: str, revision_id: str) -> Path:
    """Resolve the on-disk path for one ``(logical_name, revision_id)`` revision (AC-1-3).

    Every revision of a logical artifact is grouped under a per-artifact
    subdirectory of :meth:`revisions_dir`, keyed by ``revision_id`` (which is
    content-addressed). This is the ONLY place a revision path is derived, so
    dispatch/read/write/promotion code never duplicates the literal (AC-1-3).
    """
    return self.revisions_dir() / _slug(logical_name) / f"{_slug(revision_id)}.json"

alias_path

alias_path()

Path to the mutable logical_name -> current revision_id alias map (AC-2-1).

Resolved directly under output_root in the same manner as :meth:planning_sentinel_path — a single durable JSON file that is the only mutable pointer into the content-addressed revision store.

Source code in packages/strands-teams/src/strands_teams/layout.py
def alias_path(self) -> Path:
    """Path to the mutable ``logical_name -> current revision_id`` alias map (AC-2-1).

    Resolved directly under ``output_root`` in the same manner as
    :meth:`planning_sentinel_path` — a single durable JSON file that is the
    only mutable pointer into the content-addressed revision store.
    """
    return self.output_root / self.alias_name

persist_revision

persist_revision(revision)

Persist a revision to its content-addressed path and return it (AC-1-4, AC-1-5).

Writes the revision keyed by revision_id under :meth:revision_path so it is addressable by later reads (AC-1-4). Idempotent for an identical re-write (same revision_id and content_hash). If the same revision_id was already persisted with a DIFFERENT content_hash, the write is rejected with :class:RevisionConflictError and the stored revision is left unchanged (AC-1-5) — the store is append-only and content-addressed.

Returns:

Type Description
ArtifactRevision

The stored :class:ArtifactRevision (the argument on a fresh write, or

ArtifactRevision

the already-stored record on an idempotent re-write).

Raises:

Type Description
RevisionConflictError

If revision.revision_id is already stored with content differing from revision.content_hash (AC-1-5).

Source code in packages/strands-teams/src/strands_teams/layout.py
def persist_revision(self, revision: ArtifactRevision) -> ArtifactRevision:
    """Persist a revision to its content-addressed path and return it (AC-1-4, AC-1-5).

    Writes the revision keyed by ``revision_id`` under :meth:`revision_path` so
    it is addressable by later reads (AC-1-4). Idempotent for an identical
    re-write (same ``revision_id`` and ``content_hash``). If the same
    ``revision_id`` was already persisted with a DIFFERENT ``content_hash``,
    the write is rejected with :class:`RevisionConflictError` and the stored
    revision is left unchanged (AC-1-5) — the store is append-only and
    content-addressed.

    Returns:
        The stored :class:`ArtifactRevision` (the argument on a fresh write, or
        the already-stored record on an idempotent re-write).

    Raises:
        RevisionConflictError: If ``revision.revision_id`` is already stored
            with content differing from ``revision.content_hash`` (AC-1-5).
    """
    path = self.revision_path(revision.logical_name, revision.revision_id)
    if path.exists():
        stored = ArtifactRevision.model_validate_json(path.read_text(encoding="utf-8"))
        if stored.content_hash != revision.content_hash:
            raise RevisionConflictError(revision.revision_id, stored.content_hash, revision.content_hash)
        return stored
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(revision.model_dump_json(indent=2), encoding="utf-8")
    return revision

load_revision

load_revision(logical_name, revision_id)

Load a persisted revision, or None when it is not in the store.

Source code in packages/strands-teams/src/strands_teams/layout.py
def load_revision(self, logical_name: str, revision_id: str) -> ArtifactRevision | None:
    """Load a persisted revision, or ``None`` when it is not in the store."""
    path = self.revision_path(logical_name, revision_id)
    if not path.exists():
        return None
    return ArtifactRevision.model_validate_json(path.read_text(encoding="utf-8"))

current_revision_id

current_revision_id(logical_name)

Return the revision_id the alias currently points logical_name at.

Returns the :data:~strands_teams.models.NO_CURRENT_REVISION sentinel when logical_name has no current revision (AC-2-4).

Source code in packages/strands-teams/src/strands_teams/layout.py
def current_revision_id(self, logical_name: str) -> str:
    """Return the ``revision_id`` the alias currently points ``logical_name`` at.

    Returns the :data:`~strands_teams.models.NO_CURRENT_REVISION` sentinel when
    ``logical_name`` has no current revision (AC-2-4).
    """
    with self._locked_aliases() as aliases:
        return aliases.get(logical_name, NO_CURRENT_REVISION)

promote_revision

promote_revision(
    logical_name,
    expected_current_revision,
    proposed_revision,
)

Durably compare-and-set the alias for logical_name (AC-2-2..AC-2-5).

The compare-and-set is serialized by an OS advisory lock on the alias file, so two concurrent promotions of the same logical_name with the same expected_current_revision cannot both win: whichever acquires the lock first flips the alias, and the other observes the new current and is rejected with :class:StaleArtifactError (AC-2-5). Because the CAS is a durable operation against this on-disk store — not a mutation of the by-reference invocation_state dict — it is correct even when the callers share that dict read-only (research-coding-runtimes.yaml lines 22, 30).

On a matching CAS the proposed revision is persisted (content-addressed) with the prior current revision recorded in its supersedes lineage, and the alias is atomically flipped to it (AC-2-2). A first promotion passes the :data:~strands_teams.models.NO_CURRENT_REVISION sentinel as expected_current_revision and sets the alias as the first revision (AC-2-4).

Parameters:

Name Type Description Default
logical_name str

The logical artifact to promote.

required
expected_current_revision str

The revision the caller believes is current (the sentinel for a first promotion).

required
proposed_revision ArtifactRevision

The revision to make current. Its supersedes is augmented with the prior current revision before it is persisted.

required

Returns:

Type Description
ArtifactRevision

The persisted, now-current :class:ArtifactRevision (with the prior

ArtifactRevision

revision recorded in supersedes).

Raises:

Type Description
StaleArtifactError

If expected_current_revision does not match the alias's stored current revision — the alias is left unchanged (AC-2-3).

RevisionConflictError

If persisting the proposed revision conflicts with an already-stored revision of the same id (AC-1-5).

Source code in packages/strands-teams/src/strands_teams/layout.py
def promote_revision(
    self,
    logical_name: str,
    expected_current_revision: str,
    proposed_revision: ArtifactRevision,
) -> ArtifactRevision:
    """Durably compare-and-set the alias for ``logical_name`` (AC-2-2..AC-2-5).

    The compare-and-set is serialized by an OS advisory lock on the alias file,
    so two concurrent promotions of the same ``logical_name`` with the same
    ``expected_current_revision`` cannot both win: whichever acquires the lock
    first flips the alias, and the other observes the new current and is
    rejected with :class:`StaleArtifactError` (AC-2-5). Because the CAS is a
    durable operation against this on-disk store — not a mutation of the
    by-reference ``invocation_state`` dict — it is correct even when the callers
    share that dict read-only (research-coding-runtimes.yaml lines 22, 30).

    On a matching CAS the proposed revision is persisted (content-addressed)
    with the prior current revision recorded in its ``supersedes`` lineage, and
    the alias is atomically flipped to it (AC-2-2). A first promotion passes the
    :data:`~strands_teams.models.NO_CURRENT_REVISION` sentinel as
    ``expected_current_revision`` and sets the alias as the first revision
    (AC-2-4).

    Args:
        logical_name: The logical artifact to promote.
        expected_current_revision: The revision the caller believes is current
            (the sentinel for a first promotion).
        proposed_revision: The revision to make current. Its ``supersedes`` is
            augmented with the prior current revision before it is persisted.

    Returns:
        The persisted, now-current :class:`ArtifactRevision` (with the prior
        revision recorded in ``supersedes``).

    Raises:
        StaleArtifactError: If ``expected_current_revision`` does not match the
            alias's stored current revision — the alias is left unchanged
            (AC-2-3).
        RevisionConflictError: If persisting the proposed revision conflicts
            with an already-stored revision of the same id (AC-1-5).
    """
    with self._locked_aliases() as aliases:
        actual = aliases.get(logical_name, NO_CURRENT_REVISION)
        if actual != expected_current_revision:
            raise StaleArtifactError(logical_name, expected_current_revision, actual)

        superseded = [] if actual == NO_CURRENT_REVISION else [actual]
        merged_supersedes = list(dict.fromkeys([*proposed_revision.supersedes, *superseded]))
        to_store = proposed_revision.model_copy(update={"supersedes": merged_supersedes})

        stored = self.persist_revision(to_store)
        aliases[logical_name] = stored.revision_id
        self._write_aliases(aliases)
        return stored

approvals_dir

approvals_dir()

Directory holding the per-token_id :class:ApprovalToken store (AC-3-7).

Source code in packages/strands-teams/src/strands_teams/layout.py
def approvals_dir(self) -> Path:
    """Directory holding the per-``token_id`` :class:`ApprovalToken` store (AC-3-7)."""
    return self.output_root / self.scratch_subdir / self.approvals_subdir

approval_path

approval_path(token_id)

Resolve the on-disk path for one approval token, keyed by token_id.

Source code in packages/strands-teams/src/strands_teams/layout.py
def approval_path(self, token_id: str) -> Path:
    """Resolve the on-disk path for one approval token, keyed by ``token_id``."""
    return self.approvals_dir() / f"{_slug(token_id)}.json"

persist_approval

persist_approval(token)

Persist an :class:ApprovalToken to the approvals store and return it (AC-1-1).

Source code in packages/strands-teams/src/strands_teams/layout.py
def persist_approval(self, token: ApprovalToken) -> ApprovalToken:
    """Persist an :class:`ApprovalToken` to the approvals store and return it (AC-1-1)."""
    path = self.approval_path(token.token_id)
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(token.model_dump_json(indent=2), encoding="utf-8")
    return token

load_approval

load_approval(token_id)

Load one persisted approval token, or None when absent/unreadable.

Source code in packages/strands-teams/src/strands_teams/layout.py
def load_approval(self, token_id: str) -> ApprovalToken | None:
    """Load one persisted approval token, or ``None`` when absent/unreadable."""
    path = self.approval_path(token_id)
    if not path.exists():
        return None
    try:
        return ApprovalToken.model_validate_json(path.read_text(encoding="utf-8"))
    except (ValueError, OSError):
        return None

load_approvals

load_approvals()

Load every persisted approval token (skipping any that fail to parse — AC-3-7).

Source code in packages/strands-teams/src/strands_teams/layout.py
def load_approvals(self) -> list[ApprovalToken]:
    """Load every persisted approval token (skipping any that fail to parse — AC-3-7)."""
    approvals_dir = self.approvals_dir()
    if not approvals_dir.exists():
        return []
    tokens: list[ApprovalToken] = []
    for path in sorted(approvals_dir.glob("*.json")):
        try:
            tokens.append(ApprovalToken.model_validate_json(path.read_text(encoding="utf-8")))
        except (ValueError, OSError):
            continue
    return tokens

decision_log_path

decision_log_path()

Path to the append-only decision log — one :class:Decision per line (AC-2-4).

Source code in packages/strands-teams/src/strands_teams/layout.py
def decision_log_path(self) -> Path:
    """Path to the append-only decision log — one :class:`Decision` per line (AC-2-4)."""
    return self.output_root / self.scratch_subdir / self.decision_log_name

append_decision

append_decision(decision)

Append one :class:Decision to the decision log (AC-2-4).

Raises on I/O failure rather than swallowing it, so a transition can never proceed with its decision unrecorded (AC-2-5). The caller decides how to react; unlike the debug log, this path is auth-critical.

Raises:

Type Description
OSError

If the decision line cannot be written.

Source code in packages/strands-teams/src/strands_teams/layout.py
def append_decision(self, decision: Decision) -> None:
    """Append one :class:`Decision` to the decision log (AC-2-4).

    Raises on I/O failure rather than swallowing it, so a transition can never
    proceed with its decision unrecorded (AC-2-5). The caller decides how to
    react; unlike the debug log, this path is auth-critical.

    Raises:
        OSError: If the decision line cannot be written.
    """
    path = self.decision_log_path()
    path.parent.mkdir(parents=True, exist_ok=True)
    with path.open("a", encoding="utf-8") as f:
        f.write(decision.model_dump_json() + "\n")

human_gates_dir

human_gates_dir()

Directory holding pending :class:HumanDecision gate sentinels (AC-4-3, AC-4-5).

Source code in packages/strands-teams/src/strands_teams/layout.py
def human_gates_dir(self) -> Path:
    """Directory holding pending :class:`HumanDecision` gate sentinels (AC-4-3, AC-4-5)."""
    return self.output_root / self.scratch_subdir / self.human_gates_subdir

human_gate_path

human_gate_path(decision_id)

Resolve the on-disk path for one human-gate sentinel, keyed by decision_id.

Source code in packages/strands-teams/src/strands_teams/layout.py
def human_gate_path(self, decision_id: str) -> Path:
    """Resolve the on-disk path for one human-gate sentinel, keyed by ``decision_id``."""
    return self.human_gates_dir() / f"{_slug(decision_id)}.json"

persist_human_gate

persist_human_gate(gate)

Persist a :class:HumanDecision gate to disk and return it (AC-4-5).

A pending gate persisted here is what lets a swarm node return control rather than block past its node_timeout (AC-4-5): the gate survives on disk and the runner resumes from it later.

Source code in packages/strands-teams/src/strands_teams/layout.py
def persist_human_gate(self, gate: HumanDecision) -> HumanDecision:
    """Persist a :class:`HumanDecision` gate to disk and return it (AC-4-5).

    A pending gate persisted here is what lets a swarm node return control
    rather than block past its ``node_timeout`` (AC-4-5): the gate survives on
    disk and the runner resumes from it later.
    """
    path = self.human_gate_path(gate.decision_id)
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(gate.model_dump_json(indent=2), encoding="utf-8")
    return gate

load_human_gate

load_human_gate(decision_id)

Load one persisted human-gate sentinel, or None when absent/unreadable.

Source code in packages/strands-teams/src/strands_teams/layout.py
def load_human_gate(self, decision_id: str) -> HumanDecision | None:
    """Load one persisted human-gate sentinel, or ``None`` when absent/unreadable."""
    path = self.human_gate_path(decision_id)
    if not path.exists():
        return None
    try:
        return HumanDecision.model_validate_json(path.read_text(encoding="utf-8"))
    except (ValueError, OSError):
        return None

load_pending_human_gates

load_pending_human_gates()

Load every human gate still pending (the runner suspends on these — AC-4-3).

Source code in packages/strands-teams/src/strands_teams/layout.py
def load_pending_human_gates(self) -> list[HumanDecision]:
    """Load every human gate still ``pending`` (the runner suspends on these — AC-4-3)."""
    gates_dir = self.human_gates_dir()
    if not gates_dir.exists():
        return []
    pending: list[HumanDecision] = []
    for path in sorted(gates_dir.glob("*.json")):
        try:
            gate = HumanDecision.model_validate_json(path.read_text(encoding="utf-8"))
        except (ValueError, OSError):
            continue
        if gate.status == "pending":
            pending.append(gate)
    return pending