Loading the notebook…
Loading the notebook…
Notebooks · Specialist agents · 44
One notebook that exercises every new feature this release ships with. Use it as a runnable reference when showing the library to someone for the first time — it starts with the LLM bootstrap and ends with a multi-agent…
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__)AgentRegistry loads every JSON definition in shipit_agent/agents/agents.json, including the seven new role specialists added this release.
from shipit_agent.agents import AgentRegistry
registry = AgentRegistry.default()
agents = registry.all()
print(f"{len(agents)} agents available")
by_cat: dict[str, list[str]] = {}
for a in agents:
by_cat.setdefault(a.category, []).append(a.id)
for cat in sorted(by_cat):
print(f"\n{cat} ({len(by_cat[cat])})")
for aid in by_cat[cat][:8]:
print(f" · {aid}")
if len(by_cat[cat]) > 8:
print(f" … +{len(by_cat[cat]) - 8} more")The Autopilot class is the thing you actually reach for. It composes GoalAgent with a budget gate, checkpointing, streaming events, and (optionally) a critic loop and artifact collector.
from shipit_agent.autopilot import Autopilot, BudgetPolicy, default_heartbeat_stderr
from shipit_agent.deep import Goal
basic = Autopilot(
llm=llm,
goal=Goal(
objective="Explain list vs tuple in Python with a 3-line example.",
success_criteria=[
"Mentions mutability",
"Shows a tuple literal",
"Shows a list literal",
],
),
budget=BudgetPolicy(max_iterations=4, max_seconds=180),
on_heartbeat=default_heartbeat_stderr,
)
r = basic.run(run_id="tour-basics")
print(f"status={r.status} iters={r.iterations} halt={r.halt_reason}")
print(r.output[:500])For a dashboard-quality UI, use autopilot.stream() — an iterator of {kind, ...} dicts. The final yield is always autopilot.result.
autopilot_stream = Autopilot(
llm=llm,
goal=Goal(
objective="Write a two-line haiku about shipit_agent.",
success_criteria=["Two lines"],
),
budget=BudgetPolicy(max_iterations=3, max_seconds=120),
)
for ev in autopilot_stream.stream(run_id="tour-stream"):
kind = ev["kind"]
if kind == "autopilot.iteration":
met = ev["criteria_met"]
u = ev["usage"]
print(
f" iter {ev['iteration']} · {sum(met)}/{len(met)} met · {u['seconds']:.0f}s"
)
elif kind in (
"autopilot.run_started",
"autopilot.result",
"autopilot.criteria_satisfied",
):
print(
f" [{kind}] {ev.get('status') or ev.get('goal', {}).get('objective', '')}"
)render_stream(events, fmt=...) gives you a pretty live feed without writing your own renderer.
from shipit_agent.live_renderer import render_stream
autopilot_r = Autopilot(
llm=llm,
goal=Goal(objective="2+2?", success_criteria=["Answer is 4"]),
budget=BudgetPolicy(max_iterations=2, max_seconds=60),
)
# `plain` is notebook-friendly — no ANSI escapes.
render_stream(autopilot_r.stream(run_id="tour-render"), fmt="plain")Every iteration flushes an atomic JSON checkpoint. A crashed or halted run can pick up from its last successful iteration with autopilot.resume(run_id).
# First attempt with a deliberately tiny budget — halts partial.
first = Autopilot(
llm=llm,
goal=Goal(
objective="Describe SOLID principles with one sentence each.",
success_criteria=[f"Describes {p}" for p in ("S", "O", "L", "I", "D")],
),
budget=BudgetPolicy(max_iterations=1),
).run(run_id="tour-resume")
print(f"first: {first.status} iters={first.iterations}")
# Resume with a larger budget — keeps going.
second = Autopilot(
llm=llm,
goal=Goal(
objective="Describe SOLID principles with one sentence each.",
success_criteria=[f"Describes {p}" for p in ("S", "O", "L", "I", "D")],
),
budget=BudgetPolicy(max_iterations=6),
).resume("tour-resume")
print(f"second: {second.status} iters={second.iterations}")A persistent JSON queue at ~/.shipit_agent/autopilot-queue.json that a tick-based daemon drains one goal at a time. Perfect for a systemd / launchd unit.
from shipit_agent.scheduler_daemon import SchedulerDaemon
daemon = SchedulerDaemon(llm_factory=lambda: llm, tick_seconds=3)
daemon.enqueue(
run_id="tour-q-1",
objective="Write one sentence about Python's walrus operator.",
success_criteria=["Mentions :="],
budget={"max_iterations": 3, "max_seconds": 120},
)
for entry in daemon.list_queue():
print(f"{entry.run_id:<20} [{entry.status}] {entry.objective[:50]}")Pass critic=True to enable a default critic that uses the run's own LLM. For high-stakes goals, pass Critic(llm=reviewer_llm) with a stronger model.
autopilot_c = Autopilot(
llm=llm,
goal=Goal(
objective="Name three HTTP status codes and what they mean.",
success_criteria=["At least 3 codes", "Each code has a one-line meaning"],
),
budget=BudgetPolicy(max_iterations=5, max_seconds=180),
critic=True,
)
r = autopilot_c.run(run_id="tour-critic")
print(f"status={r.status} halt={r.halt_reason}")
print("verdict:", {k: r.critic_verdict.get(k) for k in ("confidence", "reasoning")})Code blocks and markdown docs get auto-extracted. Tools can push explicit artifacts via result metadata. The final result.artifacts is a list your UI can render as a deliverable panel.
from shipit_agent.autopilot import ArtifactCollector
collector = ArtifactCollector()
autopilot_a = Autopilot(
llm=llm,
goal=Goal(
objective="Write a Python function `fib(n)` that returns the n-th Fibonacci number.",
success_criteria=["A `def fib(n)` appears in a fenced code block"],
),
budget=BudgetPolicy(max_iterations=4, max_seconds=180),
artifacts=collector,
critic=True,
)
r = autopilot_a.run(run_id="tour-artifacts")
print(f"artifacts extracted: {len(r.artifacts)}")
for a in r.artifacts:
print(f" [{a['kind']:<9}] {a['name']} — {len(a['content'])} chars")autopilot.fanout(items, objective_template) dispatches one child per item. Each child gets a slice of the parent budget so aggregate spend stays bounded.
parent = Autopilot(
llm=llm,
goal=Goal(objective="batch-tour", success_criteria=["done"]),
budget=BudgetPolicy(
max_seconds=600, max_iterations=6, max_tool_calls=20, max_dollars=3.0
),
)
r = parent.fanout(
items=["threading", "asyncio", "multiprocessing"],
objective_template="Write a one-paragraph overview of Python's {item} module.",
criteria_template=["Mentions typical use case"],
max_parallel=3,
child_budget_frac=0.3,
)
print(f"fan-out: {r.status} · wall: {r.wall_seconds:.1f}s")
for c in r.children:
print(f" {c['run_id']:<44} {c['status']:<10} {c['iterations']} iters")A live UI over a 50-item batch needs per-child events. fanout_stream gives one autopilot.fanout_child event per completion.
events = list(
parent.fanout_stream(
items=["A", "B"],
objective_template="Summarize module {item}",
max_parallel=2,
child_budget_frac=0.2,
)
)
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']}")The three tools added this release. Each is usable standalone or as part of a specialist's tool kit.
from shipit_agent.tools.computer_use import ComputerUseTool
from shipit_agent.tools.hubspot import HubspotTool
from shipit_agent.tools.research_brief import ResearchBriefTool
cu = ComputerUseTool()
hs = HubspotTool()
rb = ResearchBriefTool()
for tool in (cu, hs, rb):
actions = (
tool.schema()["function"]["parameters"]["properties"]
.get("action", {})
.get("enum", [])
)
print(f"{tool.name:<20} actions={len(actions)} — {actions[:6]}…")Searches the web (DuckDuckGo HTML), opens the top sources, returns a structured brief with citations. No API key needed.
from shipit_agent.tools.base import ToolContext
out = rb.run(
ToolContext(prompt="demo"), query="postgres connection pooling 2026", max_sources=3
)
print(out.text[:900])screenshot is the only action most agents need — capture, then pass the PNG to a vision model for reasoning. Other actions (click, type, drag) require platform helpers (cliclick on macOS, xdotool on Linux).
out = cu.run(ToolContext(prompt="demo"), action="screenshot")
print(out.text)Every prebuilt agent has a rich prompt/role/goal/backstory and can be dropped into an Autopilot. Here we use the brand-new generalist-developer specialist with built-in tools.
from shipit_agent import Agent
from shipit_agent.builtins import get_builtin_tools
dev = Agent(
llm=llm,
prompt=registry.get("generalist-developer").prompt,
tools=get_builtin_tools(project_root="."),
max_iterations=20,
name="Generalist Developer",
)
result = dev.run(
"Add a one-line docstring to the function `BudgetPolicy.exceeded` in shipit_agent/autopilot/budget.py. Do not change behavior."
)
print(result.output[:600])The researcher + research_brief tool + Autopilot + critic = a fully-autonomous research pipeline that keeps going until the critic confirms every criterion is satisfied.
from shipit_agent.agents import AgentRegistry
researcher = AgentRegistry.default().get("researcher")
research_pilot = Autopilot(
llm=llm,
goal=Goal(
objective="Compile a one-page digest on Python type checkers (mypy / pyright / pyre).",
success_criteria=[
"At least 3 type checkers covered",
"Each has one-line tradeoff",
"Ends with a recommendation paragraph",
],
),
tools=[rb],
budget=BudgetPolicy(max_iterations=5, max_seconds=480, max_tool_calls=15),
critic=True,
artifacts=True,
prompt=researcher.prompt, # use the researcher's persona as the inner-agent prompt
)
print("Ready. Call .run(run_id='type-checker-digest') when you want it to execute.")Every feature above is wired into a CLI too. Run from a terminal, outside the notebook:
# one-shot long run
shipit autopilot "migrate SQL to parameterized form" \
--criteria "no raw concat" --criteria "tests pass" \
--max-hours 4 --format tui
# 24h queue operation
shipit queue add nightly-lint "Summarise all error lines"
shipit daemon --tick 5
# resume a checkpointed run
shipit autopilot --resume --run-id nightly-lint
Every CLI subcommand is documented via shipit <cmd> --help.
One autopilot with every feature wired — critic + artifacts + fanout + specialist prompt. Useful as a template for your own long-running runs.
mega_pilot = Autopilot(
llm=llm,
goal=Goal(objective="mega-tour", success_criteria=["done"]),
budget=BudgetPolicy(
max_seconds=600, max_iterations=6, max_tool_calls=30, max_dollars=3.0
),
critic=True,
artifacts=True,
tools=[rb],
prompt=registry.get("generalist-developer").prompt,
)
# Fan out three small jobs under the same critic+artifact umbrella.
mega_result = mega_pilot.fanout(
items=["dataclasses", "enum", "pathlib"],
objective_template="Give a 50-word description of Python's `{item}` module with one code snippet.",
criteria_template=["Under 100 words of prose", "One fenced code block"],
max_parallel=3,
child_budget_frac=0.25,
)
print(
f"mega result: {mega_result.status}, {len(mega_result.children)} children, {len(mega_result.failed)} failed"
)Everything new this release is in your hands: Autopilot's long-running runtime, three new tools, seven new specialists, the fan-out / critic / artifact triad, a scheduler daemon, a live renderer, and a CLI.
If you build something with it — ship it.