Loading the notebook…
Loading the notebook…
Notebooks · Specialist agents · 43
Three features that turn Autopilot from a solid long-runner into a desktop-grade coordinator:
Its imports and arguments are checked against shipit-agent 3.0. The outputs were saved when it was written, for an earlier release.
from pathlib import Path
import sys
# Resolve the repo root whether this cell is running from ./notebooks
# or from the repo root — mirrors the existing 01–36 notebook series.
ROOT = (
Path.cwd().resolve().parent
if Path.cwd().name == "notebooks"
else Path.cwd().resolve()
)
if str(ROOT) not in sys.path:
sys.path.insert(0, str(ROOT))
from examples.run_multi_tool_agent import build_llm_from_env
# Bedrock Llama 4 Scout is the default model this library ships with
# (see `shipit_agent/config.py`). No model name required — the helper
# reads `AWS_REGION` / credentials from env and returns an adapter that
# works with every Autopilot / Agent class.
llm = build_llm_from_env("bedrock")
print("Bedrock LLM ready:", type(llm).__name__)fanout takes a list of items and an objective template. It builds one child Autopilot per item and runs them concurrently in a thread pool. Each child gets a child_budget_frac slice of the parent budget so the aggregate cost is bounded.
from shipit_agent.autopilot import Autopilot, BudgetPolicy
from shipit_agent.deep import Goal
autopilot = Autopilot(
llm=llm,
goal=Goal(objective="fanout-parent", success_criteria=["done"]),
budget=BudgetPolicy(
max_seconds=600, max_iterations=6, max_tool_calls=30, max_dollars=3.0
),
)
# Each child inherits 20% of the parent budget — 6 children in parallel can
# therefore spend at most ~120% of the parent cap if every child fully
# utilizes. Adjust `child_budget_frac` if you need tighter global limits.
result = autopilot.fanout(
items=["PR-101", "PR-102", "PR-103", "PR-104"],
objective_template="Summarize {item} — what changed, what's risky, what to watch post-merge.",
criteria_template=["Mentions the main change", "Lists one risk"],
max_parallel=4,
child_budget_frac=0.25,
)
print(f"fan-out status: {result.status} · wall: {result.wall_seconds:.1f}s")
for c in result.children:
print(f" {c['run_id']:<40} {c['status']:<10} {c['iterations']} iters")For a live UI over a long batch, use fanout_stream — it yields one autopilot.fanout_child event the moment each child completes.
events = list(
autopilot.fanout_stream(
items=["A", "B", "C"],
objective_template="One-line summary of module {item}",
max_parallel=3,
child_budget_frac=0.15,
)
)
for ev in events:
if ev["kind"] == "autopilot.fanout_child":
print(f"child {ev['item_index']}: {ev['status']} ({ev['iterations']} iters)")
elif ev["kind"] == "autopilot.fanout_result":
print(f"final: {ev['status']} — {len(ev['children'])} children")After every iteration, the critic reviews the output against the criteria and emits a verdict. When it's confident the criteria are met (default gate: confidence >= 0.75), Autopilot stops — no more burning budget on already-satisfied goals.
The critic can be the run's own LLM (cheap self-check) or a dedicated reviewer model (stronger, 2× cost).
from shipit_agent.autopilot import Critic
# `critic=True` builds a default Critic that uses your run's LLM.
autopilot_with_critic = Autopilot(
llm=llm,
goal=Goal(
objective="Explain the Python GIL in two paragraphs and include a code snippet showing where it matters.",
success_criteria=[
"Two paragraphs of prose",
"Contains a runnable Python snippet showing GIL-related behavior",
],
),
budget=BudgetPolicy(max_seconds=300, max_iterations=6, max_tool_calls=10),
critic=True,
)
result = autopilot_with_critic.run(run_id="gil-explainer")
print(f"status={result.status} · iters={result.iterations} · halt={result.halt_reason}")
print("critic verdict:", result.critic_verdict)For a security review or production-change run, give the critic its own stronger model so the reviewer isn't marking its own homework.
# For the demo we reuse the same llm, but you'd pass a separate
# Bedrock/Anthropic adapter here. The `confidence_threshold` raises the
# gate for accepting the critic's go-ahead.
strict_critic = Critic(llm=llm, confidence_threshold=0.85)
autopilot_strict = Autopilot(
llm=llm,
goal=Goal(
objective="Audit shipit_agent/autopilot/critic.py for any obvious logic bugs.",
success_criteria=[
"At least one observation listed",
"Each observation cites a line number",
],
),
budget=BudgetPolicy(max_seconds=240, max_iterations=4, max_tool_calls=10),
critic=strict_critic,
)
print("Ready. Call .run(run_id='critic-audit') to execute.")When the agent produces code blocks, markdown docs, or tool outputs that declare themselves artifacts, the ArtifactCollector grabs them. The final AutopilotResult.artifacts is a list the caller can render as a deliverable panel.
from shipit_agent.autopilot import ArtifactCollector
collector = ArtifactCollector()
autopilot_art = Autopilot(
llm=llm,
goal=Goal(
objective="Write a Python function that reads a CSV and returns row count + column count. Include a usage example.",
success_criteria=["A `def` is present", "Usage example in a fenced block"],
),
budget=BudgetPolicy(max_seconds=240, max_iterations=4, max_tool_calls=8),
artifacts=collector,
critic=True, # combine with the critic loop
)
result = autopilot_art.run(run_id="csv-sizer")
print(f"{len(result.artifacts)} artifacts extracted:")
for a in result.artifacts:
print(f" [{a['kind']}] {a['name']} — {len(a['content'])} chars")Pass persist_dir when you want one JSON file per artifact (handy for CI runs that upload the dir as build output).
from pathlib import Path
from shipit_agent.autopilot import ArtifactCollector
out_dir = Path.home() / ".shipit_agent" / "artifacts-demo"
disk_collector = ArtifactCollector(persist_dir=out_dir)
disk_collector.add(
kind="code", name="hello.py", content="print('hi')", language="python"
)
print("files written:")
for p in out_dir.glob("*.json"):
print(" ", p.name)autopilot.stream() emits autopilot.artifact events the moment each artifact is collected — perfect for building a live deliverable dock in your UI.
autopilot_live = Autopilot(
llm=llm,
goal=Goal(
objective="Give me a Python one-liner that reverses a string.",
success_criteria=["Contains a fenced code block"],
),
budget=BudgetPolicy(max_seconds=120, max_iterations=3),
artifacts=True, # shorthand: builds a default ArtifactCollector
)
for ev in autopilot_live.stream(run_id="reverse-string"):
if ev["kind"] == "autopilot.artifact":
print(f" [artifact] {ev['artifact_kind']:<10} {ev['name']}")
elif ev["kind"] == "autopilot.result":
print(f"final: {ev['status']} with {len(ev['artifacts'])} artifacts")One Autopilot with all three features wired. Useful for a nightly run that reviews everything merged today, produces a structured digest per PR, and surfaces each as a named artifact.
from shipit_agent.autopilot import Autopilot, BudgetPolicy, Critic, ArtifactCollector
from shipit_agent.deep import Goal
parent = Autopilot(
llm=llm,
goal=Goal(objective="nightly-review", success_criteria=["per-item summary"]),
budget=BudgetPolicy(
max_seconds=600, max_iterations=4, max_tool_calls=20, max_dollars=3.0
),
critic=True,
artifacts=True,
)
# Fan out across a batch of tickets (real callers pass 50-200 IDs here).
batch_result = parent.fanout(
items=["INFRA-11", "INFRA-12", "INFRA-13"],
objective_template="Write a one-paragraph summary of ticket {item} as a markdown artifact.",
criteria_template=["One paragraph", "Starts with a ## heading"],
max_parallel=3,
child_budget_frac=0.3,
)
print(f"batch: {batch_result.status} across {len(batch_result.children)} children")
print(f"children failed: {len(batch_result.failed)}")fanout turns 'do 50 things' from overnight-sequential to minutes-parallel, with strict per-child budgets so aggregate spend is bounded.Critic adds reflection: the run terminates the moment a confident reviewer agrees the goal is met. Works with the run's own LLM or a dedicated stronger reviewer.ArtifactCollector surfaces structured deliverables (code, docs, tool outputs) — the final result and the event stream both carry them so your UI can render a proper deliverable panel.All three stack cleanly. Budget-gated, streaming, Bedrock-default. This is the long-running, goal-directed surface Claude Desktop exposes — and you can run it headless.