Loading the notebook…
Loading the notebook…
Notebooks · Teams, costs and notifications · 34
Push agent status updates to wherever your team lives. Get notified when agents start, complete, fail, or exceed budgets.
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))
import json
from shipit_agent.notifications import (
Notification,
NotificationManager,
SlackNotifier,
DiscordNotifier,
TelegramNotifier,
)
from shipit_agent.notifications.base import SEVERITY_ORDER
from shipit_agent.notifications.templates import DEFAULT_TEMPLATES, render_template
from IPython.display import display, MarkdownEvery notification has an event type, title, message, severity, metadata, and timestamp.
note = Notification(
event="run_completed",
title="Security Auditor — Run Completed",
message="Audit completed in 4.2s. Found 3 critical, 5 high vulnerabilities.",
severity="warning",
metadata={
"agent": "security-auditor",
"duration": "4.2s",
"cost": "$0.47",
"findings": "3 critical, 5 high",
},
)
print(f"Event: {note.event}")
print(f"Title: {note.title}")
print(f"Message: {note.message}")
print(f"Severity: {note.severity}")
print(f"Metadata: {note.metadata}")
print(f"Timestamp: {note.timestamp}")
print(f"\nJSON:\n{json.dumps(note.to_dict(), indent=2)}")Event: run_completed
Title: Security Auditor — Run Completed
Message: Audit completed in 4.2s. Found 3 critical, 5 high vulnerabilities.
Severity: warning
Metadata: {'agent': 'security-auditor', 'duration': '4.2s', 'cost': '$0.47', 'findings': '3 critical, 5 high'}
Timestamp: 2026-04-13 18:34:01.361020
JSON:
{
"event": "run_completed",
"title": "Security Auditor \u2014 Run Completed",
"message": "Audit completed in 4.2s. Found 3 critical, 5 high vulnerabilities.",
"severity": "warning",
"metadata": {
"agent": "security-auditor",
"duration": "4.2s",
"cost": "$0.47",
"findings": "3 critical, 5 high"
},
"timestamp": "2026-04-13T18:34:01.361020"
}print("Severity levels (lowest → highest):")
for level, order in sorted(SEVERITY_ORDER.items(), key=lambda x: x[1]):
emoji = {"info": "ℹ️", "warning": "⚠️", "error": "❌", "critical": "🚨"}[level]
print(f" {emoji} {level:10s} = {order}")
print("\nDefault templates:")
for event, template in DEFAULT_TEMPLATES.items():
print(f" {event:20s} → {template}")
# Safe rendering — missing vars stay as {var}
rendered = render_template(
DEFAULT_TEMPLATES["run_completed"],
agent="my-agent",
duration="3.1s",
cost="$0.23",
output_preview="Done!",
)
print(f"\nRendered: {rendered}")
partial = render_template(DEFAULT_TEMPLATES["run_completed"], agent="my-agent")
print(f"Partial: {partial}")Severity levels (lowest → highest):
ℹ️ info = 0
⚠️ warning = 1
❌ error = 2
🚨 critical = 3
Default templates:
run_started → {agent} started: {prompt_preview}
run_completed → {agent} completed in {duration} | Cost: {cost} | {output_preview}
tool_failed → {agent} tool '{tool}' failed: {error}
cost_alert → {agent} has spent {spent} of {budget} budget ({percent}%)
checkpoint_saved → {agent} checkpoint #{step} saved
crew_started → Crew '{crew}' started with {agent_count} agents, {task_count} tasks
crew_completed → Crew '{crew}' completed in {duration} | {completed}/{total} tasks succeeded
Rendered: my-agent completed in 3.1s | Cost: $0.23 | Done!
Partial: my-agent completed in {duration} | Cost: {cost} | {output_preview}Rich, color-coded messages using Slack's Block Kit. Zero external dependencies (uses urllib).
Setup: Create a Slack Incoming Webhook at https://api.slack.com/messaging/webhooks
slack = SlackNotifier(
webhook_url="https://hooks.slack.com/services/d//",
username="ShipIt Agent",
channel="#shipit-agent-notifications",
)
payload = slack._build_payload(note)
print("Slack Block Kit Payload:")
print(json.dumps(payload, indent=2))
print(f"\nSeverity color: {payload['attachments'][0]['color']}")
print(f"Block types: {[b['type'] for b in payload['attachments'][0]['blocks']]}")Slack Block Kit Payload:
{
"username": "ShipIt Agent",
"attachments": [
{
"color": "#f1c40f",
"blocks": [
{
"type": "header",
"text": {
"type": "plain_text",
"text": "Security Auditor \u2014 Run Completed",
"emoji": true
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "Audit completed in 4.2s. Found 3 critical, 5 high vulnerabilities."
}
},
{
"type": "section",
"fields": [
{
"type": "mrkdwn",
"text": "*agent:* security-auditor"
},
{
"type": "mrkdwn",
"text": "*duration:* 4.2s"
},
{
"type": "mrkdwn",
"text": "*cost:* $0.47"
},
{
"type": "mrkdwn",
"text": "*findings:* 3 critical, 5 high"
… 20 more linesColor-coded embeds with metadata fields.
Setup: Create a Discord Webhook in Server Settings → Integrations → Webhooks
discord = DiscordNotifier(
webhook_url="https://discord.com/api/webhooks/dd/dsdsd",
username="ShipIt Agent",
avatar_url="https://shipiit.com/logo.png",
)
payload = discord._build_payload(note)
print("Discord Embed Payload:")
print(json.dumps(payload, indent=2))
embed = payload["embeds"][0]
print(f"\nColor: {embed['color']} (hex: {hex(embed['color'])})")
print(f"Fields: {[f['name'] for f in embed.get('fields', [])]}")Discord Embed Payload:
{
"username": "ShipIt Agent",
"embeds": [
{
"title": "Security Auditor \u2014 Run Completed",
"description": "Audit completed in 4.2s. Found 3 critical, 5 high vulnerabilities.",
"color": 15965202,
"timestamp": "2026-04-13T18:34:01.361020",
"footer": {
"text": "severity: warning | event: run_completed"
},
"fields": [
{
"name": "agent",
"value": "security-auditor",
"inline": true
},
{
"name": "duration",
"value": "4.2s",
"inline": true
},
{
"name": "cost",
"value": "$0.47",
"inline": true
},
{
"name": "findings",
"value": "3 critical, 5 high",
"inline": true
}
]
}
],
"avatar_url": "https://shipiit.com/logo.png"
}
Color: 15965202 (hex: 0xf39c12)
… 1 more linesClean formatted messages via the Bot API. Special characters auto-escaped.
Setup: Create a bot via @BotFather, get bot token + chat ID
telegram = TelegramNotifier(bot_token="", chat_id="-1001237890")
formatted = telegram._format_markdown(note)
print("Telegram MarkdownV2 Message:")
print(formatted)Telegram MarkdownV2 Message: ⚠️ *Security Auditor — Run Completed* ──────────────────── Audit completed in 4\.2s\. Found 3 critical, 5 high vulnerabilities\. *agent:* security\-auditor *duration:* 4\.2s *cost:* $0\.47 *findings:* 3 critical, 5 high _2026\-04\-13 18:34:01 UTC \| warning_
Compare how each severity looks across all three notifiers.
severities = ["info", "warning", "error", "critical"]
events = ["run_started", "cost_alert", "tool_failed", "run_completed"]
print("=== Severity Comparison ===\n")
for sev in severities:
n = Notification(
event="test",
title=f"Test {sev.upper()}",
message=f"This is a {sev} notification.",
severity=sev,
metadata={"agent": "test-agent"},
)
slack_color = slack._build_payload(n)["attachments"][0]["color"]
discord_color = discord._build_embed(n)["color"]
# tg_msg = telegram._format_markdown(n).split("\n")[0] # first line has emoji
print(
f" {sev:10s} | Slack: {slack_color} | Discord: {hex(discord_color):10s} | Telegram: "
)=== Severity Comparison === info | Slack: #3498db | Discord: 0x3498db | Telegram: warning | Slack: #f1c40f | Discord: 0xf39c12 | Telegram: error | Slack: #e74c3c | Discord: 0xe74c3c | Telegram: critical | Slack: #992d22 | Discord: 0x992d22 | Telegram:
Real examples of notifications you'd send in production.
production_notifications = [
Notification(
event="run_started",
title="Security Auditor — Started",
message="Scanning /src for OWASP Top 10 vulnerabilities",
severity="info",
metadata={"agent": "security-auditor", "project": "my-api"},
),
Notification(
event="tool_failed",
title="Bash Tool Failed",
message="Permission denied: /etc/shadow",
severity="error",
metadata={"agent": "security-auditor", "tool": "bash", "error": "EACCES"},
),
Notification(
event="cost_alert",
title="Budget Warning",
message="Agent has spent $4.12 of $5.00 budget (82%)",
severity="warning",
metadata={"agent": "deep-researcher", "spent": "$4.12", "budget": "$5.00"},
),
Notification(
event="run_completed",
title="Research Crew Complete",
message="3 tasks completed in 45.2s. All passed.",
severity="info",
metadata={
"crew": "research-crew",
"tasks": "3/3",
"duration": "45.2s",
"cost": "$1.23",
},
),
Notification(
event="crew_started",
title="Deploy Pipeline Started",
message="Crew 'deploy-pipe' started with 4 agents, 6 tasks",
severity="info",
metadata={"crew": "deploy-pipe", "agents": 4, "tasks": 6},
),
]
print("Production notification examples:\n")
for n in production_notifications:
print(f" [{n.severity:8s}] {n.event:20s} — {n.title}")
print(f" {n.message}")
print(f" metadata: {n.metadata}")
print()Production notification examples:
[info ] run_started — Security Auditor — Started
Scanning /src for OWASP Top 10 vulnerabilities
metadata: {'agent': 'security-auditor', 'project': 'my-api'}
[error ] tool_failed — Bash Tool Failed
Permission denied: /etc/shadow
metadata: {'agent': 'security-auditor', 'tool': 'bash', 'error': 'EACCES'}
[warning ] cost_alert — Budget Warning
Agent has spent $4.12 of $5.00 budget (82%)
metadata: {'agent': 'deep-researcher', 'spent': '$4.12', 'budget': '$5.00'}
[info ] run_completed — Research Crew Complete
3 tasks completed in 45.2s. All passed.
metadata: {'crew': 'research-crew', 'tasks': '3/3', 'duration': '45.2s', 'cost': '$1.23'}
[info ] crew_started — Deploy Pipeline Started
Crew 'deploy-pipe' started with 4 agents, 6 tasks
metadata: {'crew': 'deploy-pipe', 'agents': 4, 'tasks': 6}Send to multiple channels. Filter by severity and event type.
manager = NotificationManager(notifiers=[slack, discord, telegram], min_severity="info")
test_notes = [
Notification(
event="run_started", title="Started", message="Starting audit", severity="info"
),
Notification(
event="tool_failed", title="Failed", message="bash failed", severity="error"
),
Notification(
event="run_completed", title="Done", message="Complete", severity="info"
),
]
print("Dispatch decisions (min_severity='info'):")
for n in test_notes:
print(f" [{n.severity:8s}] {n.event:20s} → {manager._should_notify(n)}")
# Error-only filter
error_mgr = NotificationManager(notifiers=[slack], min_severity="error")
print("\nWith min_severity='error':")
for n in test_notes:
print(f" [{n.severity:8s}] {n.event:20s} → {error_mgr._should_notify(n)}")
# Event filter
event_mgr = NotificationManager(
notifiers=[slack], events=["run_completed", "tool_failed"]
)
print("\nWith events=['run_completed', 'tool_failed']:")
for n in test_notes:
print(f" [{n.severity:8s}] {n.event:20s} → {event_mgr._should_notify(n)}")Dispatch decisions (min_severity='info'): [info ] run_started → True [error ] tool_failed → True [info ] run_completed → True With min_severity='error': [info ] run_started → False [error ] tool_failed → True [info ] run_completed → False With events=['run_completed', 'tool_failed']: [info ] run_started → False [error ] tool_failed → True [info ] run_completed → True
manager.as_hooks() returns AgentHooks that automatically send notifications on agent events.
# Production usage:
manager = NotificationManager([
SlackNotifier(webhook_url=os.environ["SLACK_WEBHOOK"]),
DiscordNotifier(webhook_url=os.environ["DISCORD_WEBHOOK"]),
])
agent = Agent.with_builtins(
llm=llm,
hooks=manager.as_hooks(agent_name="security-auditor"),
)
result = agent.run("Audit my code")
# → Slack & Discord get: "security-auditor started: Audit my code"
# → Slack & Discord get: "security-auditor completed in 4.2s"
Here's the full pattern for wiring notifications into a Bedrock agent. Replace webhook URLs with real ones.
# Full production wiring pattern
from shipit_agent import Agent
from examples.run_multi_tool_agent import build_llm_from_env
llm = build_llm_from_env("bedrock")
# In production, use real URLs from environment:
# import os
# slack_real = SlackNotifier(webhook_url=os.environ["SLACK_WEBHOOK"])
# discord_real = DiscordNotifier(webhook_url=os.environ["DISCORD_WEBHOOK"])
# telegram_real = TelegramNotifier(
# bot_token=os.environ["TELEGRAM_TOKEN"],
# chat_id=os.environ["TELEGRAM_CHAT_ID"],
# )
# Wire notifications + cost tracking together
from shipit_agent.costs import CostTracker, Budget
tracker = CostTracker(budget=Budget(max_dollars=2.00))
notify_hooks = manager.as_hooks(agent_name="demo-agent")
cost_hooks = tracker.as_hooks()
# Merge both hook sets into a single AgentHooks object.
notify_hooks.before_llm.extend(cost_hooks.before_llm)
notify_hooks.after_llm.extend(cost_hooks.after_llm)
notify_hooks.before_tool.extend(cost_hooks.before_tool)
notify_hooks.after_tool.extend(cost_hooks.after_tool)
agent = Agent.with_builtins(
llm=llm,
prompt="You are a helpful assistant.",
hooks=notify_hooks,
)
result = agent.run("Explain the CAP theorem in 3 sentences.")
# After the run, you'd send notifications:
completion_note = Notification(
event="run_completed",
title="Agent Complete",
message=f"Cost: ${tracker.total_cost:.4f} | Tokens: {tracker.total_tokens['input_tokens'] + tracker.total_tokens['output_tokens']:,}",
severity="info",
metadata={
"cost": f"${tracker.total_cost:.4f}",
"input_tokens": tracker.total_tokens["input_tokens"],
"output_tokens": tracker.total_tokens["output_tokens"],
},
)
print("Would send this notification:")
print(f" {completion_note.title}: {completion_note.message}")
print("\nSlack payload preview:")
print(json.dumps(slack._build_payload(completion_note), indent=2)[:500])
display(Markdown(result.output[:400]))No pricing data for model 'bedrock/openai.gpt-oss-120b-1:0' — cost will be $0.00
Would send this notification:
Agent Complete: Cost: $0.0000 | Tokens: 12,096
Slack payload preview:
{
"username": "ShipIt Agent",
"attachments": [
{
"color": "#3498db",
"blocks": [
{
"type": "header",
"text": {
"type": "plain_text",
"text": "Agent Complete",
"emoji": true
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "Cost: $0.0000 | Tokens: 12,096"
}
},
{
"type": "section",
"fieldThe CAP theorem states that a distributed data system can only simultaneously guarantee two of the following three properties: Consistency (every read receives the most recent write), Availability (every request receives a response, without guarantee that it contains the latest data), and Partition tolerance (the system continues to operate despite network partitions). Because network
hooks = manager.as_hooks(agent_name="security-auditor")
print(f"Hooks type: {type(hooks).__name__}")
print("\nEvents that trigger notifications:")
print(" • on_before_llm → run_started (first call only)")
print(" • on_after_llm → run_completed (final LLM response only)")
print(" • on_after_tool → tool_failed (on error)")Hooks type: AgentHooks Events that trigger notifications: • on_before_llm → run_started (first call only) • on_after_llm → run_completed • on_after_tool → tool_failed (on error)
custom_mgr = NotificationManager(
notifiers=[slack],
templates={
"run_started": "🚀 {agent} is working on: {prompt_preview}",
"run_completed": "✅ {agent} done! ({duration}) — Cost: {cost}",
"tool_failed": "🔥 ALERT: {agent} → {tool} failed: {error}",
},
)
rendered = render_template(
"✅ {agent} done! ({duration}) — Cost: {cost}",
agent="auditor",
duration="4.2s",
cost="$0.47",
)
print(f"Custom: {rendered}")Custom: ✅ auditor done! (4.2s) — Cost: $0.47
Budget alerts as notifications.
from shipit_agent.costs import CostTracker, Budget
cost_alert = Notification(
event="cost_alert",
title="Budget Warning",
message=render_template(
DEFAULT_TEMPLATES["cost_alert"],
agent="deep-researcher",
spent="$4.12",
budget="$5.00",
percent="82",
),
severity="warning",
metadata={"agent": "deep-researcher", "spent": "$4.12", "budget": "$5.00"},
)
print(f"Title: {cost_alert.title}")
print(f"Message: {cost_alert.message}")
print(f"Severity: {cost_alert.severity}")
print(
f"\nSlack color: {slack._build_payload(cost_alert)['attachments'][0]['color']} (yellow = warning)"
)Title: Budget Warning Message: deep-researcher has spent $4.12 of $5.00 budget (82%) Severity: warning Slack color: #f1c40f (yellow = warning)
| Feature | API |
|---|---|
| Create notification | Notification(event=..., title=..., message=..., severity=...) |
| Slack | SlackNotifier(webhook_url=...) |
| Discord | DiscordNotifier(webhook_url=...) |
| Telegram | TelegramNotifier(bot_token=..., chat_id=...) |
| Multi-channel | NotificationManager(notifiers=[slack, discord, telegram]) |
| Auto-hook | hooks=manager.as_hooks("my-agent") |
| Filter severity | NotificationManager(..., min_severity="error") |
| Filter events | NotificationManager(..., events=["run_completed"]) |
| Custom templates | NotificationManager(..., templates={...}) |
| Safe rendering | render_template(template, agent="...", cost="...") |
| Budget alerts | Notification(event="cost_alert", ...) |
Zero external dependencies. Works with any webhook URL. Production-ready.