Loading the notebook…
Loading the notebook…
Notebooks · Workspace, live UI and observability · 70
This notebook runs fully offline — a scripted LLM stands in so you can execute every cell with no API keys.
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
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 shipit_agent import (
Agent,
AgentScheduler,
format_activity,
format_event_line,
list_mcp_catalog,
)
from shipit_agent.llms.base import LLMResponse, ToolCall
print("imports OK")imports OK
Agent.for_role40+ prebuilt role definitions become runnable agents in one line. Each role brings its own system prompt (role + goal + backstory + playbook) and selects its own tools from the builtin catalogue.
class ScriptedLLM:
"""Offline stand-in: first turn calls build_document, then answers."""
def __init__(self):
self.turn = 0
def complete(self, *, messages, tools=None, **_):
self.turn += 1
if self.turn == 1:
return LLMResponse(tool_calls=[ToolCall(
name="build_document",
arguments={
"kind": "xlsx",
"title": "Q2 Close",
"sheets": [{
"name": "P&L",
"headers": ["Item", "Amount"],
"rows": [
["Revenue", 124_000],
["Costs", -78_500],
["Net", "=B2+B3"], # live formula
],
}],
},
)])
return LLMResponse(content="Q2 close workbook is ready — net income formula included.")
for role in ("finance-analyst", "marketing-writer", "researcher",
"figma-designer", "sales-rep", "generalist-developer"):
a = Agent.for_role(role, llm=ScriptedLLM())
print(f"{role:<24} {len(a.tools):>2} tools · {a.metadata['category']}")finance-analyst 6 tools · Finance marketing-writer 8 tools · Marketing researcher 3 tools · Research figma-designer 4 tools · Design sales-rep 6 tools · Sales generalist-developer 6 tools · Engineering
# Unknown ids fail helpfully with did-you-mean suggestions:
try:
Agent.for_role("finance", llm=ScriptedLLM())
except ValueError as e:
print(e)Unknown role 'finance'. Did you mean: finance-analyst?
connect_mcpConnect well-known MCP servers by name. Required env vars and the launcher binary are validated before anything starts.
for entry in list_mcp_catalog():
env = f" (needs {', '.join(entry.required_env)})" if entry.required_env else ""
print(f"{entry.name:<14} {entry.description}{env}")brave-search Web search via the Brave Search API. (needs BRAVE_API_KEY) fetch Fetch a URL and return page content as markdown. filesystem Read/write files under the directories you pass as args. github Repos, issues, PRs, code search on GitHub. (needs GITHUB_TOKEN) gitlab GitLab projects, issues, and merge requests. (needs GITLAB_PERSONAL_ACCESS_TOKEN) google-maps Places, directions, and geocoding via Google Maps. (needs GOOGLE_MAPS_API_KEY) memory Persistent knowledge-graph memory across runs. postgres Query PostgreSQL (read-only). Pass the connection URL as an arg. puppeteer Headless browser: navigate, screenshot, interact with pages. sentry Look up Sentry issues and stack traces. (needs SENTRY_AUTH_TOKEN) slack Post and read Slack messages and channels. (needs SLACK_BOT_TOKEN, SLACK_TEAM_ID) sqlite Query a SQLite database file passed as an arg.
# Fail-fast: misconfiguration is one clear message, not a subprocess stack trace.
from shipit_agent import connect_mcp
import os
os.environ.pop("SLACK_BOT_TOKEN", None)
try:
connect_mcp("slack")
except ValueError as e:
print(e)
# When configured, it's one line into the agent:
# agent = Agent.with_builtins(llm=llm, mcps=[connect_mcp("github")])MCP server 'slack' needs env var(s): SLACK_BOT_TOKEN, SLACK_TEAM_ID. Pass them via env={...} or export them.The finance analyst builds a genuine .xlsx (styled headers, frozen panes, a live formula), and format_activity renders the run as Claude-Code-style tool cards — name, args, ✓/✗, duration, output preview.
agent = Agent.for_role("finance-analyst", llm=ScriptedLLM())
result = agent.run("Close Q2 and hand me the workbook.")
print(format_activity(result))
print()
print("Final answer:", result.output)⚙ build_document(kind="xlsx", title="Q2 Close", sheets=[{'name': 'P&L', 'headers': ['Item', 'A…) ✓ 252ms
└ Created XLSX 'Q2 Close' → .shipit_workspace/documents/q2_close.xlsx (5,109 bytes)
✔ run completed · 1 tool call · 2 iterations
Final answer: Q2 close workbook is ready — net income formula included.# Verify the workbook is real — reopen it and check the live formula.
import openpyxl
done = [e for e in result.events if e.type == "tool_completed"][0]
path = [t.metadata["path"] for t in result.tool_results if t.metadata.get("path")][0]
wb = openpyxl.load_workbook(path)
ws = wb["P&L"]
print("header bold: ", ws["A1"].font.bold)
print("frozen panes: ", ws.freeze_panes)
print("live formula: ", ws["B4"].value)
print("duration_ms: ", done.payload["duration_ms"])header bold: True frozen panes: A2 live formula: =B2+B3 duration_ms: 251.9
# Live rendering for streams — format_event_line returns a line per user-facing event.
agent2 = Agent.for_role("finance-analyst", llm=ScriptedLLM())
for event in agent2.stream("Close Q2 again."):
line = format_event_line(event)
if line:
print(line)⚙ build_document(kind="xlsx", title="Q2 Close", sheets=[{'name': 'P&L', 'headers': ['Item', 'A…) …
⚙ build_document ✓ 3ms
└ Created XLSX 'Q2 Close' → .shipit_workspace/documents/q2_close.xlsx (5,109 bytes)AgentSchedulerCron for agents: every= seconds, at="HH:MM" daily, or cron="..." (optional croniter). The clock/sleep hooks are injectable, so we can simulate 26 hours instantly.
class FakeClock:
def __init__(self, start=1_000_000.0): self.now = start
def __call__(self): return self.now
def advance(self, s): self.now += s
class TinyLLM:
def complete(self, *, messages, **_):
return LLMResponse(content="[done]")
clock = FakeClock()
agent3 = Agent(llm=TinyLLM(), auto_use_skills=False)
sched = AgentScheduler(agent3, clock=clock, sleep=lambda s: None)
log = []
sched.add("Summarize new error logs.", every=3600,
name="hourly-digest", on_result=lambda r: log.append("hourly"))
sched.add("Draft today's product post.", at="09:00",
name="daily-post", on_result=lambda r: log.append("daily"))
for hour in range(26): # simulate 26 hours — instant
clock.advance(3600)
sched.run_pending()
print(f"hourly-digest fired {log.count('hourly')}x, daily-post fired {log.count('daily')}x")
print("In production: sched.run_forever() # blocks, firing jobs as due")hourly-digest fired 26x, daily-post fired 1x In production: sched.run_forever() # blocks, firing jobs as due
from shipit_agent import Agent, AgentScheduler, connect_mcp, format_activity
agent = Agent.for_role(
"finance-analyst",
llm=llm, # any provider
mcps=[connect_mcp("postgres", args=["postgresql://localhost/finance"])],
)
sched = AgentScheduler(agent)
sched.add(
"Pull this month's ledger, reconcile it, and build the close package: "
"P&L workbook + board deck.",
at="07:00",
on_result=lambda r: print(format_activity(r.agent_result)),
)
sched.run_forever()
One sector specialist, live database access over MCP, polished Excel and PowerPoint out, readable logs, on a schedule.
See also: docs/guides/super-agent.md · examples 20_scheduled_jobs.py, 21_growth_research_agent.py, 22_super_agent.py