Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 110 additions & 0 deletions alembic/versions/0017_image_digest_allowlist.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
"""BASE-produced image digest allowlist + revocation denylists.

Adds durable storage for mechanism 4 (digest allowlist) of prism-lium image
attestation. Lookup rules live in ``base.compute.digest_allowlist``; these
tables only persist registered bindings and denylist entries.

Revision ID: 0017_digest_allowlist
Revises: 0016_watcher_state
Create Date: 2026-07-26 00:00:00.000000
"""

from __future__ import annotations

from collections.abc import Sequence

import sqlalchemy as sa

from alembic import op

revision: str = "0017_digest_allowlist"
down_revision: str | None = "0016_watcher_state"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None


def upgrade() -> None:
"""Apply the migration."""

op.create_table(
"image_digest_allowlist",
sa.Column("id", sa.Uuid(as_uuid=True), nullable=False),
sa.Column("commit_sha", sa.Text(), nullable=False),
sa.Column("tree_sha", sa.Text(), nullable=False),
sa.Column("variant", sa.Text(), nullable=False),
sa.Column("digest", sa.Text(), nullable=False),
sa.Column(
"created_at",
sa.DateTime(timezone=True),
server_default=sa.func.now(),
nullable=False,
),
sa.Column(
"updated_at",
sa.DateTime(timezone=True),
server_default=sa.func.now(),
nullable=False,
),
sa.PrimaryKeyConstraint("id", name=op.f("pk_image_digest_allowlist")),
sa.UniqueConstraint("digest", name="uq_image_digest_allowlist_digest"),
sa.UniqueConstraint(
"commit_sha",
"tree_sha",
"variant",
name="uq_image_digest_allowlist_commit_tree_variant",
),
)
op.create_index(
"ix_image_digest_allowlist_commit_sha",
"image_digest_allowlist",
["commit_sha"],
unique=False,
)
op.create_index(
"ix_image_digest_allowlist_variant",
"image_digest_allowlist",
["variant"],
unique=False,
)

op.create_table(
"denied_image_digests",
sa.Column("digest", sa.Text(), nullable=False),
sa.Column("reason", sa.Text(), nullable=True),
sa.Column(
"created_at",
sa.DateTime(timezone=True),
server_default=sa.func.now(),
nullable=False,
),
sa.PrimaryKeyConstraint("digest", name=op.f("pk_denied_image_digests")),
)

op.create_table(
"denied_image_commits",
sa.Column("commit_sha", sa.Text(), nullable=False),
sa.Column("reason", sa.Text(), nullable=True),
sa.Column(
"created_at",
sa.DateTime(timezone=True),
server_default=sa.func.now(),
nullable=False,
),
sa.PrimaryKeyConstraint("commit_sha", name=op.f("pk_denied_image_commits")),
)


def downgrade() -> None:
"""Revert the migration."""

op.drop_table("denied_image_commits")
op.drop_table("denied_image_digests")
op.drop_index(
"ix_image_digest_allowlist_variant",
table_name="image_digest_allowlist",
)
op.drop_index(
"ix_image_digest_allowlist_commit_sha",
table_name="image_digest_allowlist",
)
op.drop_table("image_digest_allowlist")
75 changes: 75 additions & 0 deletions alembic/versions/0018_attestation_nonces.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
"""BASE-issued single-use attestation nonces for prism-lium constation.

Adds durable storage for mechanism 1 (nonce-bound attestation). Issue/consume
rules live in ``base.compute.attestation_nonce``; this table only persists
issued nonces and their consume timestamps (BASE clocks only).

Revision ID: 0018_attestation_nonces
Revises: 0017_digest_allowlist
Create Date: 2026-07-26 00:00:00.000000
"""

from __future__ import annotations

from collections.abc import Sequence

import sqlalchemy as sa

from alembic import op

revision: str = "0018_attestation_nonces"
down_revision: str | None = "0017_digest_allowlist"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None


def upgrade() -> None:
"""Apply the migration."""

op.create_table(
"attestation_nonces",
sa.Column("nonce", sa.Text(), nullable=False),
sa.Column("work_unit_id", sa.Text(), nullable=False),
sa.Column("miner_hotkey", sa.Text(), nullable=False),
sa.Column("pod_id", sa.Text(), nullable=False),
sa.Column("issued_at", sa.DateTime(timezone=True), nullable=False),
sa.Column("expires_at", sa.DateTime(timezone=True), nullable=False),
sa.Column("consumed_at", sa.DateTime(timezone=True), nullable=True),
sa.PrimaryKeyConstraint("nonce", name=op.f("pk_attestation_nonces")),
)
op.create_index(
"ix_attestation_nonces_work_unit_id",
"attestation_nonces",
["work_unit_id"],
unique=False,
)
op.create_index(
"ix_attestation_nonces_miner_hotkey",
"attestation_nonces",
["miner_hotkey"],
unique=False,
)
op.create_index(
"ix_attestation_nonces_expires_at",
"attestation_nonces",
["expires_at"],
unique=False,
)


def downgrade() -> None:
"""Revert the migration."""

op.drop_index(
"ix_attestation_nonces_expires_at",
table_name="attestation_nonces",
)
op.drop_index(
"ix_attestation_nonces_miner_hotkey",
table_name="attestation_nonces",
)
op.drop_index(
"ix_attestation_nonces_work_unit_id",
table_name="attestation_nonces",
)
op.drop_table("attestation_nonces")
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ echo "Starting login process..."
# Check if Docker credentials exist
if [[ -n "$DSTACK_DOCKER_USERNAME" && -n "$DSTACK_DOCKER_PASSWORD" ]]; then
echo "Docker credentials found"
DOCKER_REGISTRY_TARGET="${DSTACK_DOCKER_REGISTRY:-docker.io}"
DOCKER_REGISTRY_TARGET="${DSTACK_DOCKER_REGISTRY:-ghcr.io}"
echo "Target Docker registry: $DOCKER_REGISTRY_TARGET"

# Check if already logged in
Expand Down
23 changes: 13 additions & 10 deletions packages/challenges/agent-challenge/golden/live-registry-refs.json
Original file line number Diff line number Diff line change
@@ -1,25 +1,28 @@
{
"schema": "harbor-independence/live-registry-refs@1",
"note": "SIDE manifest (NOT the frozen golden dataset digest). Maps a small deterministic subset of Terminal-Bench 2.1 task_ids to PULLABLE, digest-pinned registry refs published to the miner's public Docker Hub namespace so an in-CVM DooD orchestrator can `docker pull` them for a live smoke E2E. This file does NOT affect golden/dataset-digest.json, its per-task content_digest_sha256, the canonical_content_digest_sha256, or the canonical compose/measurement. Resolution is opt-in and fail-closed (see agent_challenge.canonical.live_registry).",
"note": "SIDE manifest (NOT the frozen golden dataset digest). Maps a small deterministic subset of Terminal-Bench 2.1 task_ids to PULLABLE, digest-pinned GHCR refs under ghcr.io/baseintelligence so an in-CVM DooD orchestrator can pull them for a live smoke E2E. D6: only GHCR shipping refs are accepted (see agent_challenge.canonical.live_registry). This file does NOT affect golden/dataset-digest.json, its per-task content_digest_sha256, the canonical_content_digest_sha256, or the canonical compose/measurement. Resolution is opt-in and fail-closed. OPS_REQUIRED: orchestrator_image digest is the T1 local buildx manifest (not yet pushed to GHCR); task image digests retain prior content hashes under the new GHCR path pattern and must be republished to GHCR before live pull succeeds.",
"dataset": "terminal-bench/terminal-bench-2-1",
"namespace": "docker.io/mathiiss",
"orchestrator_image": "docker.io/mathiiss/agent-challenge-canonical@sha256:02331f0909f617e333f113be376d353770a673669946bcddaac3c53cbde7c9d8",
"orchestrator_image_source": "services.yaml build-canonical (`uv run python -m agent_challenge.canonical.build --build`) built reproducibly (BuildKit SOURCE_DATE_EPOCH + rewrite-timestamp, provenance/sbom off) and pushed to the miner Docker Hub namespace",
"namespace": "ghcr.io/baseintelligence",
"orchestrator_image": "ghcr.io/baseintelligence/agent-challenge-canonical@sha256:ea399cee1b3c9015024918a6070901e6b7b4bee432c8afe1e7dd40a95547e0bc",
"orchestrator_image_source": "T1 local agent-recipe buildx (SOURCE_DATE_EPOCH + rewrite-timestamp, provenance/sbom off) → manifest sha256:ea399cee1b3c9015024918a6070901e6b7b4bee432c8afe1e7dd40a95547e0bc; tag was ghcr.io/baseintelligence/agent-challenge-canonical:t1-local. OPS_REQUIRED: replace with the first published GHCR digest from agent-recipe publish-eval-image.yml before T3/prod pin.",
"tasks": {
"adaptive-rejection-sampler": {
"registry_ref": "docker.io/mathiiss/agent-challenge-tb21-adaptive-rejection-sampler@sha256:7c8bd5835f19506222805de68d65f83d6cca5b502f7d71dc5e2d9d4dd447c0c8",
"registry_ref": "ghcr.io/baseintelligence/agent-challenge-tb21-adaptive-rejection-sampler@sha256:7c8bd5835f19506222805de68d65f83d6cca5b502f7d71dc5e2d9d4dd447c0c8",
"source_ref": "alexgshaw/adaptive-rejection-sampler:20251031",
"content_digest_sha256": "bcaa2399985cd57666018025846289ab25e193ae0dd8fb7f0ffab2410c24d4de"
"content_digest_sha256": "bcaa2399985cd57666018025846289ab25e193ae0dd8fb7f0ffab2410c24d4de",
"ops_status": "BLOCKED_REPUBLISH — path retargeted to GHCR; digest is historical content pin until image is pushed under ghcr.io/baseintelligence/"
},
"bn-fit-modify": {
"registry_ref": "docker.io/mathiiss/agent-challenge-tb21-bn-fit-modify@sha256:c0371862a0861f282eb206471452ea9aa8d494e3687bdc8167c96ab39d73db50",
"registry_ref": "ghcr.io/baseintelligence/agent-challenge-tb21-bn-fit-modify@sha256:c0371862a0861f282eb206471452ea9aa8d494e3687bdc8167c96ab39d73db50",
"source_ref": "alexgshaw/bn-fit-modify:20251031",
"content_digest_sha256": "b5f9644970c17ad9ddb46b7266f7bcd87c761d77d7e6f55d7cfe7284d5ff66e9"
"content_digest_sha256": "b5f9644970c17ad9ddb46b7266f7bcd87c761d77d7e6f55d7cfe7284d5ff66e9",
"ops_status": "BLOCKED_REPUBLISH — path retargeted to GHCR; digest is historical content pin until image is pushed under ghcr.io/baseintelligence/"
},
"break-filter-js-from-html": {
"registry_ref": "docker.io/mathiiss/agent-challenge-tb21-break-filter-js-from-html@sha256:2a5bd51bab582993befc1a24252c03af673cb1db7cf7d0b2195d66a42a3872a7",
"registry_ref": "ghcr.io/baseintelligence/agent-challenge-tb21-break-filter-js-from-html@sha256:2a5bd51bab582993befc1a24252c03af673cb1db7cf7d0b2195d66a42a3872a7",
"source_ref": "alexgshaw/break-filter-js-from-html:20251031",
"content_digest_sha256": "678008d1a4fd1e6e1b9b3cc9a327219fe4b410a31eafc52e9099bbf947eea600"
"content_digest_sha256": "678008d1a4fd1e6e1b9b3cc9a327219fe4b410a31eafc52e9099bbf947eea600",
"ops_status": "BLOCKED_REPUBLISH — path retargeted to GHCR; digest is historical content pin until image is pushed under ghcr.io/baseintelligence/"
}
}
}
115 changes: 115 additions & 0 deletions packages/challenges/agent-challenge/scripts/miner_agent/_miner_loop.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
"""Autonomous solve loop for one Terminal-Bench task."""

from __future__ import annotations

import time
from typing import Any, Protocol

from _miner_tools import TOOLS, dispatch_tool, parse_tool_arguments

SYSTEM_PROMPT = """You are an autonomous coding agent solving a Terminal-Bench task.
You run non-interactively inside a container. Never ask questions. Never wait for humans.

Rules:
- Explore with shell_command (pwd, ls, find, cat) before editing.
- Prefer small, correct changes. Verify with commands after edits.
- Do not read or modify hidden test harness files under /tests unless the instruction requires it.
- Do not commit secrets. Do not print API keys.
- When the task is fully done, respond with a short plain-text summary and NO tool calls.
- If stuck after several attempts, summarize what you tried and stop.
"""


class LLMClientProto(Protocol):
def chat(
self,
messages: list[dict[str, Any]],
tools: list[dict[str, Any]] | None = None,
) -> dict[str, Any]: ...


async def run_solve_loop(
*,
instruction: str,
environment: Any,
llm: LLMClientProto,
extra_env: dict[str, str] | None = None,
max_steps: int = 40,
task_timeout_sec: float = 900.0,
command_env: dict[str, str] | None = None,
) -> str:
"""Drive LLM ↔ shell until completion, timeout, or step budget.

Never raises for recoverable LLM/tool failures — returns a miss summary.
"""
started = time.monotonic()
messages: list[dict[str, Any]] = [
{"role": "system", "content": SYSTEM_PROMPT},
{
"role": "user",
"content": (
f"Task instruction:\n{instruction}\n\n"
"Start by inspecting the workspace, then solve the task completely."
),
},
]

last_summary = "miss: no progress"
for step in range(max_steps):
if time.monotonic() - started > task_timeout_sec:
return f"miss: task timeout after {task_timeout_sec:.1f}s (step {step})"

try:
response = llm.chat(messages, tools=TOOLS)
except Exception as exc: # noqa: BLE001 — miss, do not abort suite
return f"miss: llm error: {type(exc).__name__}: {exc}"

content = str(response.get("content") or "")
tool_calls = response.get("tool_calls")

# Enforce wall-clock even when the LLM call itself overran the budget.
if time.monotonic() - started > task_timeout_sec:
return f"miss: task timeout after {task_timeout_sec:.1f}s (step {step})"

if not tool_calls:
if content.strip():
return content.strip()
return last_summary

# Record assistant turn with tool_calls for the next round.
assistant_msg: dict[str, Any] = {"role": "assistant", "content": content or None}
assistant_msg["tool_calls"] = tool_calls
messages.append(assistant_msg)

for call in tool_calls:
if time.monotonic() - started > task_timeout_sec:
return f"miss: task timeout after {task_timeout_sec:.1f}s (during tools)"
call_id = str(call.get("id") or f"call_{step}")
fn = call.get("function") or {}
name = str(fn.get("name") or "")
args = parse_tool_arguments(fn.get("arguments"))
if not name:
result = "error: missing tool name"
else:
result = await dispatch_tool(
environment,
name,
args,
extra_env=command_env,
)
last_summary = f"step {step + 1}: {name}"
messages.append(
{
"role": "tool",
"tool_call_id": call_id,
"content": result,
}
)

# Bound context growth: keep system + user + last N messages.
if len(messages) > 60:
head = messages[:2]
tail = messages[-40:]
messages = head + [{"role": "user", "content": "[earlier steps compacted]"}] + tail

return f"miss: exceeded max_steps={max_steps}; last={last_summary}"
Loading