superkick 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CLA.md +91 -0
- data/CLAUDE.md +2226 -0
- data/CONTRIBUTING.md +104 -0
- data/LICENSE +108 -0
- data/LICENSE-COMMERCIAL.md +39 -0
- data/PLAN.md +161 -0
- data/README.md +1155 -0
- data/exe/superkick +6 -0
- data/lib/superkick/agent/runtime.rb +82 -0
- data/lib/superkick/agent/runtimes/local.rb +74 -0
- data/lib/superkick/agent/runtimes.rb +4 -0
- data/lib/superkick/agent.rb +209 -0
- data/lib/superkick/agent_store.rb +85 -0
- data/lib/superkick/attach/client.rb +245 -0
- data/lib/superkick/attach/protocol.rb +71 -0
- data/lib/superkick/attach/server.rb +371 -0
- data/lib/superkick/budget_checker.rb +120 -0
- data/lib/superkick/buffer/client.rb +91 -0
- data/lib/superkick/buffer/server.rb +127 -0
- data/lib/superkick/cli/agent.rb +524 -0
- data/lib/superkick/cli/completion.rb +591 -0
- data/lib/superkick/cli/goal.rb +71 -0
- data/lib/superkick/cli/mcp.rb +34 -0
- data/lib/superkick/cli/monitor.rb +47 -0
- data/lib/superkick/cli/notifier.rb +39 -0
- data/lib/superkick/cli/repository.rb +46 -0
- data/lib/superkick/cli/server.rb +106 -0
- data/lib/superkick/cli/setup.rb +166 -0
- data/lib/superkick/cli/spawner.rb +85 -0
- data/lib/superkick/cli/team.rb +407 -0
- data/lib/superkick/cli.rb +175 -0
- data/lib/superkick/client_registry.rb +30 -0
- data/lib/superkick/configuration.rb +178 -0
- data/lib/superkick/connection.rb +56 -0
- data/lib/superkick/control/client.rb +78 -0
- data/lib/superkick/control/reply.rb +43 -0
- data/lib/superkick/control/server.rb +1271 -0
- data/lib/superkick/cost_accumulator.rb +53 -0
- data/lib/superkick/cost_extractor.rb +65 -0
- data/lib/superkick/cost_poller.rb +70 -0
- data/lib/superkick/driver/profile_source.rb +134 -0
- data/lib/superkick/driver.rb +179 -0
- data/lib/superkick/drivers/claude_code.rb +110 -0
- data/lib/superkick/drivers/codex.rb +57 -0
- data/lib/superkick/drivers/copilot.rb +75 -0
- data/lib/superkick/drivers/gemini.rb +86 -0
- data/lib/superkick/drivers/goose.rb +74 -0
- data/lib/superkick/drivers.rb +16 -0
- data/lib/superkick/drop.rb +80 -0
- data/lib/superkick/drops.rb +76 -0
- data/lib/superkick/environment_executor.rb +90 -0
- data/lib/superkick/goal.rb +95 -0
- data/lib/superkick/goals/agent_exit.rb +41 -0
- data/lib/superkick/goals/agent_signal.rb +42 -0
- data/lib/superkick/goals/command.rb +103 -0
- data/lib/superkick/history_buffer.rb +38 -0
- data/lib/superkick/hosted/attach/bridge.rb +52 -0
- data/lib/superkick/hosted/attach/client.rb +208 -0
- data/lib/superkick/hosted/attach/relay.rb +313 -0
- data/lib/superkick/hosted/attach/relay_store.rb +48 -0
- data/lib/superkick/hosted/bridge.rb +263 -0
- data/lib/superkick/hosted/buffer/bridge.rb +42 -0
- data/lib/superkick/hosted/buffer/client.rb +63 -0
- data/lib/superkick/hosted/buffer/relay.rb +126 -0
- data/lib/superkick/hosted/buffer/relay_store.rb +42 -0
- data/lib/superkick/hosted/control/client.rb +84 -0
- data/lib/superkick/hosted/mcp_proxy.rb +144 -0
- data/lib/superkick/inject_handler.rb +24 -0
- data/lib/superkick/injection_guard.rb +26 -0
- data/lib/superkick/injection_queue.rb +177 -0
- data/lib/superkick/injector.rb +65 -0
- data/lib/superkick/input_buffer.rb +171 -0
- data/lib/superkick/integrations/bugsnag/README.md +98 -0
- data/lib/superkick/integrations/bugsnag/spawner.rb +307 -0
- data/lib/superkick/integrations/bugsnag/templates/error_opened.liquid +17 -0
- data/lib/superkick/integrations/bugsnag.rb +7 -0
- data/lib/superkick/integrations/circleci/README.md +75 -0
- data/lib/superkick/integrations/circleci/monitor.rb +185 -0
- data/lib/superkick/integrations/circleci/probe.rb +36 -0
- data/lib/superkick/integrations/circleci/templates/ci_failure.liquid +8 -0
- data/lib/superkick/integrations/circleci/templates/ci_success.liquid +1 -0
- data/lib/superkick/integrations/circleci.rb +8 -0
- data/lib/superkick/integrations/datadog/README.md +253 -0
- data/lib/superkick/integrations/datadog/alert_goal.rb +94 -0
- data/lib/superkick/integrations/datadog/alert_monitor.rb +163 -0
- data/lib/superkick/integrations/datadog/alert_spawner.rb +201 -0
- data/lib/superkick/integrations/datadog/notification_templates/default.liquid +10 -0
- data/lib/superkick/integrations/datadog/notifier.rb +294 -0
- data/lib/superkick/integrations/datadog/spawner.rb +201 -0
- data/lib/superkick/integrations/datadog/templates/alert_changed.liquid +8 -0
- data/lib/superkick/integrations/datadog/templates/alert_escalated.liquid +8 -0
- data/lib/superkick/integrations/datadog/templates/alert_recovered.liquid +14 -0
- data/lib/superkick/integrations/datadog/templates/alert_triggered.liquid +29 -0
- data/lib/superkick/integrations/datadog/templates/error_opened.liquid +15 -0
- data/lib/superkick/integrations/datadog.rb +14 -0
- data/lib/superkick/integrations/docker/README.md +256 -0
- data/lib/superkick/integrations/docker/client.rb +295 -0
- data/lib/superkick/integrations/docker/runtime.rb +218 -0
- data/lib/superkick/integrations/docker.rb +4 -0
- data/lib/superkick/integrations/git/repository_source.rb +66 -0
- data/lib/superkick/integrations/git/version_control.rb +119 -0
- data/lib/superkick/integrations/git.rb +8 -0
- data/lib/superkick/integrations/github/README.md +300 -0
- data/lib/superkick/integrations/github/check_failed_spawner.rb +199 -0
- data/lib/superkick/integrations/github/drops.rb +114 -0
- data/lib/superkick/integrations/github/goal.rb +135 -0
- data/lib/superkick/integrations/github/issue_goal.rb +104 -0
- data/lib/superkick/integrations/github/issue_spawner.rb +160 -0
- data/lib/superkick/integrations/github/monitor.rb +251 -0
- data/lib/superkick/integrations/github/probe.rb +30 -0
- data/lib/superkick/integrations/github/repository_source.rb +228 -0
- data/lib/superkick/integrations/github/templates/check_failed.liquid +10 -0
- data/lib/superkick/integrations/github/templates/ci_failure.liquid +5 -0
- data/lib/superkick/integrations/github/templates/ci_success.liquid +1 -0
- data/lib/superkick/integrations/github/templates/issue_opened.liquid +20 -0
- data/lib/superkick/integrations/github/templates/pr_comment.liquid +2 -0
- data/lib/superkick/integrations/github/templates/pr_review.liquid +4 -0
- data/lib/superkick/integrations/github.rb +16 -0
- data/lib/superkick/integrations/honeybadger/README.md +97 -0
- data/lib/superkick/integrations/honeybadger/notification_templates/default.liquid +8 -0
- data/lib/superkick/integrations/honeybadger/notifier.rb +250 -0
- data/lib/superkick/integrations/honeybadger/spawner.rb +214 -0
- data/lib/superkick/integrations/honeybadger/templates/error_opened.liquid +17 -0
- data/lib/superkick/integrations/honeybadger.rb +9 -0
- data/lib/superkick/integrations/shell/README.md +83 -0
- data/lib/superkick/integrations/shell/monitor.rb +87 -0
- data/lib/superkick/integrations/shell/templates/shell_alert.liquid +6 -0
- data/lib/superkick/integrations/shell/templates/shell_success.liquid +6 -0
- data/lib/superkick/integrations/shell.rb +7 -0
- data/lib/superkick/integrations/shortcut/README.md +193 -0
- data/lib/superkick/integrations/shortcut/drops.rb +91 -0
- data/lib/superkick/integrations/shortcut/monitor.rb +582 -0
- data/lib/superkick/integrations/shortcut/probe.rb +34 -0
- data/lib/superkick/integrations/shortcut/spawner.rb +264 -0
- data/lib/superkick/integrations/shortcut/templates/related_story_changed.liquid +6 -0
- data/lib/superkick/integrations/shortcut/templates/story_blocker.liquid +8 -0
- data/lib/superkick/integrations/shortcut/templates/story_comment.liquid +5 -0
- data/lib/superkick/integrations/shortcut/templates/story_description_changed.liquid +19 -0
- data/lib/superkick/integrations/shortcut/templates/story_owner_changed.liquid +10 -0
- data/lib/superkick/integrations/shortcut/templates/story_ready.liquid +41 -0
- data/lib/superkick/integrations/shortcut/templates/story_state_changed.liquid +9 -0
- data/lib/superkick/integrations/shortcut/templates/story_unblocked.liquid +5 -0
- data/lib/superkick/integrations/shortcut.rb +11 -0
- data/lib/superkick/integrations/slack/README.md +297 -0
- data/lib/superkick/integrations/slack/drops.rb +70 -0
- data/lib/superkick/integrations/slack/notifier.rb +426 -0
- data/lib/superkick/integrations/slack/spawner.rb +251 -0
- data/lib/superkick/integrations/slack/templates/default.liquid +17 -0
- data/lib/superkick/integrations/slack/templates/slack_reply.liquid +3 -0
- data/lib/superkick/integrations/slack/templates/spawn/slack_message.liquid +10 -0
- data/lib/superkick/integrations/slack/thread_monitor.rb +161 -0
- data/lib/superkick/integrations/slack.rb +12 -0
- data/lib/superkick/liquid.rb +129 -0
- data/lib/superkick/local/repository_source.rb +148 -0
- data/lib/superkick/mcp_server.rb +596 -0
- data/lib/superkick/monitor.rb +215 -0
- data/lib/superkick/notification_dispatcher.rb +280 -0
- data/lib/superkick/notifier.rb +173 -0
- data/lib/superkick/notifier_state_store.rb +55 -0
- data/lib/superkick/notifier_template.rb +121 -0
- data/lib/superkick/notifiers/command.rb +124 -0
- data/lib/superkick/notifiers/terminal_bell.rb +41 -0
- data/lib/superkick/output_logger.rb +54 -0
- data/lib/superkick/poller.rb +126 -0
- data/lib/superkick/process_runner.rb +87 -0
- data/lib/superkick/pty_proxy.rb +403 -0
- data/lib/superkick/registry.rb +75 -0
- data/lib/superkick/repository_source.rb +195 -0
- data/lib/superkick/server.rb +211 -0
- data/lib/superkick/session_recorder.rb +154 -0
- data/lib/superkick/setup.rb +160 -0
- data/lib/superkick/spawn/agent_spawner.rb +311 -0
- data/lib/superkick/spawn/approval_store.rb +113 -0
- data/lib/superkick/spawn/handler.rb +144 -0
- data/lib/superkick/spawn/injector.rb +119 -0
- data/lib/superkick/spawn/workflow_executor.rb +196 -0
- data/lib/superkick/spawn/workflow_validator.rb +77 -0
- data/lib/superkick/spawner.rb +67 -0
- data/lib/superkick/supervisor.rb +516 -0
- data/lib/superkick/team/artifact_store.rb +92 -0
- data/lib/superkick/team/log.rb +140 -0
- data/lib/superkick/team/log_entry_drop.rb +34 -0
- data/lib/superkick/team/log_monitor.rb +84 -0
- data/lib/superkick/team/log_notifier.rb +96 -0
- data/lib/superkick/team/log_store.rb +40 -0
- data/lib/superkick/template_filters.rb +24 -0
- data/lib/superkick/template_renderer.rb +223 -0
- data/lib/superkick/templates/team_log/planning_agent.liquid +38 -0
- data/lib/superkick/templates/team_log/team_digest.liquid +45 -0
- data/lib/superkick/templates/team_log/teammate_message.liquid +7 -0
- data/lib/superkick/templates/team_log/worker_kickoff.liquid +37 -0
- data/lib/superkick/templates/workflow/workflow_triggered.liquid +22 -0
- data/lib/superkick/version.rb +5 -0
- data/lib/superkick/version_control.rb +135 -0
- data/lib/superkick/yaml_config.rb +302 -0
- data/lib/superkick.rb +198 -0
- data/plan.md +267 -0
- metadata +404 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "faraday"
|
|
4
|
+
|
|
5
|
+
module Superkick
|
|
6
|
+
module Integrations
|
|
7
|
+
module Datadog
|
|
8
|
+
# Watches Datadog Error Tracking for open error groups and spawns AI coding
|
|
9
|
+
# agents to fix them.
|
|
10
|
+
#
|
|
11
|
+
# Polls the Datadog Error Tracking API for error groups matching configurable
|
|
12
|
+
# filters (service, environment, status, source). Tracks dispatched group IDs
|
|
13
|
+
# in-memory to avoid re-dispatching.
|
|
14
|
+
#
|
|
15
|
+
# Config keys:
|
|
16
|
+
# site (optional) — Datadog site, default "datadoghq.com"
|
|
17
|
+
# api_key (optional) — API key, falls back to $DD_API_KEY
|
|
18
|
+
# application_key (optional) — Application key, falls back to $DD_APP_KEY
|
|
19
|
+
# service (optional) — filter to a specific service name
|
|
20
|
+
# environment (optional) — filter to an environment, default "production"
|
|
21
|
+
# source (optional) — error source filter (e.g. "ruby", "python")
|
|
22
|
+
# minimum_events (optional) — minimum event count to dispatch, default 1
|
|
23
|
+
# query (optional) — raw Datadog search query string
|
|
24
|
+
class Spawner < Superkick::Spawner
|
|
25
|
+
attr_reader :conn
|
|
26
|
+
|
|
27
|
+
DEFAULT_SITE = "datadoghq.com"
|
|
28
|
+
DEFAULT_ENVIRONMENT = "production"
|
|
29
|
+
DEFAULT_MINIMUM_EVENTS = 1
|
|
30
|
+
PER_PAGE = 25
|
|
31
|
+
|
|
32
|
+
def self.type = :datadog
|
|
33
|
+
|
|
34
|
+
def self.description
|
|
35
|
+
"Watches Datadog Error Tracking for open error groups and spawns AI " \
|
|
36
|
+
"coding agents to fix them. Supports filtering by service, " \
|
|
37
|
+
"environment, source, and minimum event count."
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def self.required_config = %i[]
|
|
41
|
+
|
|
42
|
+
def self.spawn_templates_dir
|
|
43
|
+
File.join(__dir__, "templates")
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def self.agent_id(event)
|
|
47
|
+
"datadog-error-#{event[:group_id]}"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def self.setup_label = "Datadog Errors"
|
|
51
|
+
|
|
52
|
+
def self.setup_config
|
|
53
|
+
<<~YAML
|
|
54
|
+
datadog_errors:
|
|
55
|
+
type: datadog
|
|
56
|
+
# api_key: <%= env("DD_API_KEY") %>
|
|
57
|
+
# application_key: <%= env("DD_APP_KEY") %>
|
|
58
|
+
# site: datadoghq.com # Datadog site (default)
|
|
59
|
+
# service: my-service # filter by service name
|
|
60
|
+
# environment: production # default
|
|
61
|
+
# source: ruby # error source filter
|
|
62
|
+
# minimum_events: 1 # minimum events before spawning
|
|
63
|
+
# max_duration: 3600
|
|
64
|
+
YAML
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def initialize(name:, config:, handler:, connection: nil)
|
|
68
|
+
super(name:, config:, handler:)
|
|
69
|
+
@conn = connection
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def tick
|
|
73
|
+
groups = fetch_error_groups
|
|
74
|
+
groups.each do |group|
|
|
75
|
+
attrs = group["attributes"] || {}
|
|
76
|
+
|
|
77
|
+
next if filtered_by_minimum_events?(attrs)
|
|
78
|
+
|
|
79
|
+
@seen_group_ids.add(group["id"])
|
|
80
|
+
|
|
81
|
+
dispatch(
|
|
82
|
+
event_type: :error_opened,
|
|
83
|
+
group_id: group["id"],
|
|
84
|
+
error_class: attrs["name"].to_s,
|
|
85
|
+
message: attrs["message"].to_s,
|
|
86
|
+
status: attrs["status"].to_s,
|
|
87
|
+
service: attrs["service"].to_s,
|
|
88
|
+
environment: attrs["env"].to_s,
|
|
89
|
+
first_seen: attrs["first_seen"].to_s,
|
|
90
|
+
last_seen: attrs["last_seen"].to_s,
|
|
91
|
+
events: attrs["count"] || 0,
|
|
92
|
+
users: attrs["impacted_users"],
|
|
93
|
+
source: attrs["source"].to_s,
|
|
94
|
+
url: error_group_url(group["id"])
|
|
95
|
+
)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def on_start
|
|
100
|
+
@conn ||= build_connection
|
|
101
|
+
@seen_group_ids = Set.new
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
private
|
|
105
|
+
|
|
106
|
+
# -- Datadog Error Tracking API -------------------------------------------
|
|
107
|
+
|
|
108
|
+
def fetch_error_groups
|
|
109
|
+
query = build_query
|
|
110
|
+
params = {
|
|
111
|
+
"filter[query]" => query,
|
|
112
|
+
"filter[status]" => "open",
|
|
113
|
+
"page[limit]" => PER_PAGE.to_s,
|
|
114
|
+
"sort" => "-last_seen"
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
Superkick.logger.debug(log_tag) { "Fetching error groups: #{params.inspect}" }
|
|
118
|
+
|
|
119
|
+
resp = get("/api/v2/error-tracking/issues", params)
|
|
120
|
+
return [] unless resp
|
|
121
|
+
|
|
122
|
+
body = resp.body
|
|
123
|
+
groups = body["data"]
|
|
124
|
+
return [] unless groups.is_a?(Array)
|
|
125
|
+
|
|
126
|
+
new_groups = groups.reject { @seen_group_ids.include?(it["id"]) }
|
|
127
|
+
|
|
128
|
+
Superkick.logger.info(log_tag) { "Found #{groups.size} error groups, #{new_groups.size} new" }
|
|
129
|
+
new_groups
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def build_query
|
|
133
|
+
parts = []
|
|
134
|
+
|
|
135
|
+
env = self[:environment]
|
|
136
|
+
env = DEFAULT_ENVIRONMENT if env.nil?
|
|
137
|
+
parts << "env:#{env}" if env && !env.to_s.empty?
|
|
138
|
+
|
|
139
|
+
parts << "service:#{self[:service]}" if self[:service] && !self[:service].to_s.empty?
|
|
140
|
+
parts << "source:#{self[:source]}" if self[:source] && !self[:source].to_s.empty?
|
|
141
|
+
|
|
142
|
+
# Allow raw query passthrough for advanced filtering
|
|
143
|
+
parts << self[:query].to_s if self[:query] && !self[:query].to_s.empty?
|
|
144
|
+
|
|
145
|
+
parts.join(" ")
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# -- Client-side filters --------------------------------------------------
|
|
149
|
+
|
|
150
|
+
def filtered_by_minimum_events?(attrs)
|
|
151
|
+
min = self[:minimum_events] || DEFAULT_MINIMUM_EVENTS
|
|
152
|
+
count = attrs["count"] || 0
|
|
153
|
+
count < min
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# -- URL helpers ----------------------------------------------------------
|
|
157
|
+
|
|
158
|
+
def error_group_url(group_id)
|
|
159
|
+
site = self[:site] || DEFAULT_SITE
|
|
160
|
+
"https://app.#{site}/error-tracking/issue/#{group_id}"
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# -- HTTP -----------------------------------------------------------------
|
|
164
|
+
|
|
165
|
+
def get(path, params = {})
|
|
166
|
+
resp = @conn.get(path) do |req|
|
|
167
|
+
params.each { |k, v| req.params[k] = v }
|
|
168
|
+
end
|
|
169
|
+
return nil unless handle_response!(resp)
|
|
170
|
+
resp
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def handle_response!(resp)
|
|
174
|
+
case resp.status
|
|
175
|
+
when 200..299 then true
|
|
176
|
+
when 401, 403 then raise FatalError, "Datadog auth failed (HTTP #{resp.status})"
|
|
177
|
+
when 429 then raise RateLimited, "Datadog rate limited"
|
|
178
|
+
when 404
|
|
179
|
+
Superkick.logger.warn(log_tag) { "Datadog 404: resource not found" }
|
|
180
|
+
false
|
|
181
|
+
else
|
|
182
|
+
Superkick.logger.warn(log_tag) { "Datadog HTTP #{resp.status}" }
|
|
183
|
+
false
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def build_connection
|
|
188
|
+
site = self[:site] || DEFAULT_SITE
|
|
189
|
+
api_key = self[:api_key] || ENV["DD_API_KEY"]
|
|
190
|
+
app_key = self[:application_key] || ENV["DD_APP_KEY"]
|
|
191
|
+
|
|
192
|
+
Faraday.new(url: "https://api.#{site}") do |f|
|
|
193
|
+
f.headers["DD-API-KEY"] = api_key if api_key
|
|
194
|
+
f.headers["DD-APPLICATION-KEY"] = app_key if app_key
|
|
195
|
+
f.response :json
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
SUPERKICK [{{ "now" | time }}]: Datadog alert status changed — {{ name }}
|
|
2
|
+
{% if url -%}
|
|
3
|
+
URL: {{ url }}
|
|
4
|
+
{% endif -%}
|
|
5
|
+
Status: {{ status }} (was {{ previous_status }})
|
|
6
|
+
|
|
7
|
+
The Datadog monitor "{{ name }}" changed status from {{ previous_status }} to {{ status }}.
|
|
8
|
+
Take this into account as you continue your investigation.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
SUPERKICK [{{ "now" | time }}]: Datadog alert escalated — {{ name }}
|
|
2
|
+
{% if url -%}
|
|
3
|
+
URL: {{ url }}
|
|
4
|
+
{% endif -%}
|
|
5
|
+
Status: {{ status }} (was {{ previous_status }})
|
|
6
|
+
|
|
7
|
+
The Datadog monitor "{{ name }}" has escalated from {{ previous_status }} to {{ status }}.
|
|
8
|
+
This alert is getting worse — prioritize your investigation accordingly.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
SUPERKICK [{{ "now" | time }}]: Datadog alert recovered — {{ name }}
|
|
2
|
+
{% if url -%}
|
|
3
|
+
URL: {{ url }}
|
|
4
|
+
{% endif -%}
|
|
5
|
+
Status: {{ status }} (was {{ previous_status }})
|
|
6
|
+
|
|
7
|
+
The Datadog monitor "{{ name }}" has recovered and is now OK.
|
|
8
|
+
|
|
9
|
+
If you have already implemented a fix, this confirms it is working.
|
|
10
|
+
Please signal your goal as completed using `superkick_signal_goal`
|
|
11
|
+
with status "completed" and a brief summary of what you did.
|
|
12
|
+
|
|
13
|
+
If you have not yet started working on a fix, this alert resolved on its own.
|
|
14
|
+
Signal your goal as completed with a summary noting the alert self-recovered.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
SUPERKICK [{{ "now" | time }}]: Datadog alert — {{ name }}
|
|
2
|
+
{% if url -%}
|
|
3
|
+
URL: {{ url }}
|
|
4
|
+
{% endif -%}
|
|
5
|
+
Status: {{ status }}{% if alert_type %} | Type: {{ alert_type }}{% endif %}{% if priority %} | Priority: P{{ priority }}{% endif %}
|
|
6
|
+
{% if tags.size > 0 -%}
|
|
7
|
+
Tags: {{ tags | join: ", " }}
|
|
8
|
+
{% endif -%}
|
|
9
|
+
|
|
10
|
+
## Alert: {{ name }}
|
|
11
|
+
|
|
12
|
+
{% if message -%}
|
|
13
|
+
### Runbook / Notification Message
|
|
14
|
+
{{ message }}
|
|
15
|
+
{% endif -%}
|
|
16
|
+
|
|
17
|
+
{% if query -%}
|
|
18
|
+
### Monitor Query
|
|
19
|
+
```
|
|
20
|
+
{{ query }}
|
|
21
|
+
```
|
|
22
|
+
{% endif -%}
|
|
23
|
+
|
|
24
|
+
Please triage this alert:
|
|
25
|
+
1. Review the monitor query and notification message above for context
|
|
26
|
+
2. Check application logs and recent code changes for potential root causes
|
|
27
|
+
3. If this is a code issue, identify the root cause and implement a fix
|
|
28
|
+
4. If this is infrastructure/config related, document your findings and recommendations
|
|
29
|
+
5. Run tests to verify your fix doesn't introduce regressions
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
SUPERKICK [{{ "now" | time }}]: Datadog error — {{ error_class }}
|
|
2
|
+
{% if url -%}
|
|
3
|
+
URL: {{ url }}
|
|
4
|
+
{% endif -%}
|
|
5
|
+
Service: {{ service }}{% unless environment == "" %} | Env: {{ environment }}{% endunless %}
|
|
6
|
+
Events: {{ events }}{% if users %} | Users: {{ users }}{% endif %}
|
|
7
|
+
First seen: {{ first_seen }} | Last seen: {{ last_seen }}
|
|
8
|
+
|
|
9
|
+
## Error
|
|
10
|
+
|
|
11
|
+
**{{ error_class }}**: {% if message == "" %}(no message){% else %}{{ message }}{% endif %}
|
|
12
|
+
|
|
13
|
+
Please investigate this error, identify the root cause, implement a fix, and
|
|
14
|
+
write tests to prevent regression. Check the Datadog Error Tracking dashboard
|
|
15
|
+
for full stack traces and contextual data.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "datadog/notifier"
|
|
4
|
+
require_relative "datadog/spawner"
|
|
5
|
+
require_relative "datadog/alert_spawner"
|
|
6
|
+
require_relative "datadog/alert_monitor"
|
|
7
|
+
require_relative "datadog/alert_goal"
|
|
8
|
+
|
|
9
|
+
module Superkick
|
|
10
|
+
Notifier.register(Integrations::Datadog::Notifier)
|
|
11
|
+
Spawner.register(Integrations::Datadog::Spawner)
|
|
12
|
+
Spawner.register(Integrations::Datadog::AlertSpawner)
|
|
13
|
+
Monitor.register(Integrations::Datadog::AlertMonitor)
|
|
14
|
+
end
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
# Docker Runtime
|
|
2
|
+
|
|
3
|
+
Type: `:docker`
|
|
4
|
+
|
|
5
|
+
Provisions spawned agents in Docker containers via the Docker Engine API.
|
|
6
|
+
|
|
7
|
+
## Server connectivity
|
|
8
|
+
|
|
9
|
+
Containerized agents need to communicate with the Superkick server. Two modes are supported, and local mode works automatically with no extra configuration.
|
|
10
|
+
|
|
11
|
+
### Local mode (automatic)
|
|
12
|
+
|
|
13
|
+
When the Superkick server uses the default local transport (Unix sockets), the Docker runtime automatically bind-mounts the Superkick `run/` directory into the container and sets `SUPERKICK_DIR` so the agent process can find the server's control socket. No manual volume configuration needed — just set the image and go:
|
|
14
|
+
|
|
15
|
+
```yaml
|
|
16
|
+
runtime:
|
|
17
|
+
type: docker
|
|
18
|
+
docker:
|
|
19
|
+
image: superkick/agent:latest
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Under the hood, the runtime injects:
|
|
23
|
+
- A bind mount: `~/.superkick/run` → `/superkick/run` (inside the container)
|
|
24
|
+
- An env var: `SUPERKICK_DIR=/superkick`
|
|
25
|
+
|
|
26
|
+
If you need to override the container-internal path, set `SUPERKICK_DIR` explicitly in the `env:` config — your value takes precedence over the auto-injected default.
|
|
27
|
+
|
|
28
|
+
### Hosted mode (HTTPS + WebSocket)
|
|
29
|
+
|
|
30
|
+
When the container can't access the host filesystem (remote Docker daemons, orchestrators, multi-host setups), configure hosted mode. The agent connects to the server over HTTPS for control and WebSocket for buffer communication. In this mode, no auto-injection occurs — the agent uses the `server:` config instead of Unix sockets:
|
|
31
|
+
|
|
32
|
+
```yaml
|
|
33
|
+
server:
|
|
34
|
+
url: https://superkick.example.com
|
|
35
|
+
api_key: <%= env("SUPERKICK_API_KEY") %>
|
|
36
|
+
|
|
37
|
+
runtime:
|
|
38
|
+
type: docker
|
|
39
|
+
docker:
|
|
40
|
+
image: superkick/agent:latest
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Configuration
|
|
44
|
+
|
|
45
|
+
```yaml
|
|
46
|
+
runtime:
|
|
47
|
+
type: docker
|
|
48
|
+
docker:
|
|
49
|
+
image: superkick/agent:latest # required — Docker image to use
|
|
50
|
+
host: unix:///var/run/docker.sock # Docker daemon address (default)
|
|
51
|
+
pull_policy: missing # when to pull the image
|
|
52
|
+
memory: 4G # container memory limit
|
|
53
|
+
cpu: 2 # CPU core limit (NanoCPUs)
|
|
54
|
+
network: superkick-net # Docker network to attach
|
|
55
|
+
stop_timeout: 30 # seconds before SIGKILL on stop
|
|
56
|
+
auto_remove: true # remove container after exit
|
|
57
|
+
env: # environment variables added to every container
|
|
58
|
+
GITHUB_TOKEN: <%= env("GITHUB_TOKEN") %>
|
|
59
|
+
volumes: # bind mounts (host:container[:options])
|
|
60
|
+
- /home/user/.ssh:/root/.ssh:ro
|
|
61
|
+
- /home/user/.gitconfig:/root/.gitconfig:ro
|
|
62
|
+
labels: # container labels
|
|
63
|
+
app: superkick
|
|
64
|
+
environment: production
|
|
65
|
+
privileged: false # run in privileged mode (for DinD)
|
|
66
|
+
cap_add: # Linux capabilities to add
|
|
67
|
+
- SYS_ADMIN
|
|
68
|
+
security_opt: # security options
|
|
69
|
+
- seccomp:unconfined
|
|
70
|
+
container_runtime: sysbox-runc # OCI runtime (for unprivileged DinD)
|
|
71
|
+
registry: # private registry authentication
|
|
72
|
+
username: <%= env("DOCKER_REGISTRY_USER") %>
|
|
73
|
+
password: <%= env("DOCKER_REGISTRY_PASSWORD") %>
|
|
74
|
+
server: ghcr.io
|
|
75
|
+
tls: # TLS client certificates for TCP connections
|
|
76
|
+
ca: /path/to/ca.pem
|
|
77
|
+
cert: /path/to/cert.pem
|
|
78
|
+
key: /path/to/key.pem
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Settings reference
|
|
82
|
+
|
|
83
|
+
| Setting | Default | Description |
|
|
84
|
+
|---------|---------|-------------|
|
|
85
|
+
| `image` | *(required)* | Docker image name with optional tag (e.g. `superkick/agent:latest`) |
|
|
86
|
+
| `host` | `unix:///var/run/docker.sock` | Docker daemon address. Supports `unix://` and `tcp://` schemes |
|
|
87
|
+
| `pull_policy` | `missing` | When to pull the image: `always` (every provision), `missing` (only if not present locally), `never` (fail if not present) |
|
|
88
|
+
| `memory` | *(none)* | Memory limit. Accepts human-readable strings (`512M`, `4G`) or byte integers |
|
|
89
|
+
| `cpu` | *(none)* | CPU limit in cores (e.g. `2` = 2,000,000,000 NanoCPUs) |
|
|
90
|
+
| `network` | *(none)* | Docker network name to attach the container to |
|
|
91
|
+
| `stop_timeout` | `30` | Seconds to wait after SIGTERM before sending SIGKILL |
|
|
92
|
+
| `auto_remove` | `true` | Automatically remove the container when it exits |
|
|
93
|
+
| `env` | `{}` | Additional environment variables merged into every container. Spawner-level env vars take precedence on conflict |
|
|
94
|
+
| `volumes` | `[]` | Bind mount strings in `host:container[:options]` format |
|
|
95
|
+
| `labels` | `{}` | Labels applied to every container. `superkick.agent_id` is always added automatically |
|
|
96
|
+
| `privileged` | `false` | Run container in privileged mode. Required for Docker-in-Docker (DinD) |
|
|
97
|
+
| `cap_add` | `[]` | Linux capabilities to add to the container (e.g. `["SYS_ADMIN"]`) |
|
|
98
|
+
| `security_opt` | `[]` | Security options (e.g. `["seccomp:unconfined"]`) |
|
|
99
|
+
| `container_runtime` | *(none)* | OCI runtime to use for the container (e.g. `sysbox-runc`). Maps to Docker's `HostConfig.Runtime` |
|
|
100
|
+
| `registry` | *(none)* | Private registry credentials (`username`, `password`, `server`) sent as Base64-encoded auth header |
|
|
101
|
+
| `tls` | *(none)* | TLS client certificate paths (`ca`, `cert`, `key`) for TCP connections to the Docker daemon |
|
|
102
|
+
|
|
103
|
+
## Container naming
|
|
104
|
+
|
|
105
|
+
Containers are named `superkick-<agent-id>` and labeled with `superkick.agent_id` for identification and cleanup.
|
|
106
|
+
|
|
107
|
+
## Image pull policy
|
|
108
|
+
|
|
109
|
+
| Policy | Behavior |
|
|
110
|
+
|--------|----------|
|
|
111
|
+
| `always` | Pull the image before every provision, even if it exists locally |
|
|
112
|
+
| `missing` | Check if the image exists locally; pull only if absent (default) |
|
|
113
|
+
| `never` | Never pull; fail if the image is not available locally |
|
|
114
|
+
|
|
115
|
+
## Connection modes
|
|
116
|
+
|
|
117
|
+
The Docker client supports two connection modes:
|
|
118
|
+
|
|
119
|
+
- **Unix socket** (default) — connects to the Docker daemon via `unix:///var/run/docker.sock`. Uses a custom Faraday adapter for HTTP-over-Unix-socket communication.
|
|
120
|
+
- **TCP + TLS** — connects to a remote Docker daemon via `tcp://host:port`. When `tls:` config is provided, uses mutual TLS with client certificates.
|
|
121
|
+
|
|
122
|
+
## Docker-in-Docker (DinD)
|
|
123
|
+
|
|
124
|
+
When agents need to run Docker commands inside their containers (e.g. `docker compose up`, devcontainer builds, running test suites that spin up services), there are several approaches with different security and isolation trade-offs.
|
|
125
|
+
|
|
126
|
+
### Option 1: Socket passthrough (Docker-outside-of-Docker)
|
|
127
|
+
|
|
128
|
+
Mount the host Docker socket into the container. The agent shares the host's Docker daemon, so containers it creates are **siblings** of the agent container on the host. This is the simplest approach and what the [original `dind` author recommends](https://jpetazzo.github.io/2015/09/03/do-not-use-docker-in-docker-for-ci/) for most CI/CD use cases.
|
|
129
|
+
|
|
130
|
+
```yaml
|
|
131
|
+
runtime:
|
|
132
|
+
type: docker
|
|
133
|
+
docker:
|
|
134
|
+
image: superkick/agent:latest
|
|
135
|
+
volumes:
|
|
136
|
+
- /var/run/docker.sock:/var/run/docker.sock
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
**Pros:** Zero overhead, no special image or capabilities needed, full Docker feature parity.
|
|
140
|
+
|
|
141
|
+
**Cons:** No isolation — the agent can see and manipulate all host containers. Sibling containers persist on the host after the agent exits (no automatic cleanup). Volume mounts inside agent-created containers reference host paths, not the agent container's filesystem.
|
|
142
|
+
|
|
143
|
+
**When to use:** Single-tenant environments where you trust the agent, or when you need maximum Docker compatibility with minimal setup.
|
|
144
|
+
|
|
145
|
+
### Option 2: True DinD (privileged)
|
|
146
|
+
|
|
147
|
+
Run a full Docker daemon inside the agent container. The agent gets a completely isolated Docker environment with its own image cache, networks, and volumes. When the outer container is removed, everything inside — including all nested containers — is destroyed automatically.
|
|
148
|
+
|
|
149
|
+
The agent image must include `dockerd`. The official [`docker:dind`](https://hub.docker.com/_/docker) image is a good base, or you can install Docker Engine in your own image.
|
|
150
|
+
|
|
151
|
+
```yaml
|
|
152
|
+
runtime:
|
|
153
|
+
type: docker
|
|
154
|
+
docker:
|
|
155
|
+
image: superkick/agent-dind:latest
|
|
156
|
+
privileged: true
|
|
157
|
+
volumes:
|
|
158
|
+
# Use a named volume or tmpfs for the inner daemon's data —
|
|
159
|
+
# overlay-on-overlay is slow and fragile
|
|
160
|
+
- superkick-dind-storage:/var/lib/docker
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
The container's entrypoint should start `dockerd` in the background before launching the AI CLI. A minimal example:
|
|
164
|
+
|
|
165
|
+
```dockerfile
|
|
166
|
+
FROM docker:dind
|
|
167
|
+
# ... install your AI CLI, superkick, etc.
|
|
168
|
+
|
|
169
|
+
COPY entrypoint.sh /entrypoint.sh
|
|
170
|
+
ENTRYPOINT ["/entrypoint.sh"]
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
#!/bin/sh
|
|
175
|
+
# entrypoint.sh — start dockerd, wait for it, then exec the agent command
|
|
176
|
+
dockerd &
|
|
177
|
+
# Wait for the daemon socket to appear
|
|
178
|
+
while ! docker info >/dev/null 2>&1; do sleep 0.5; done
|
|
179
|
+
exec "$@"
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
**Pros:** Full isolation. Automatic cleanup. The agent's Docker environment is ephemeral — no state leaks between runs.
|
|
183
|
+
|
|
184
|
+
**Cons:** `privileged: true` grants the container nearly all host capabilities, which is a broad security surface. The inner daemon needs its own storage (see storage driver notes below). Slightly slower startup while `dockerd` initializes.
|
|
185
|
+
|
|
186
|
+
**When to use:** Multi-tenant environments where isolation matters more than minimizing the privilege surface, or when automatic cleanup of nested containers is important.
|
|
187
|
+
|
|
188
|
+
### Option 3: True DinD (targeted capabilities)
|
|
189
|
+
|
|
190
|
+
A more locked-down alternative to full privileged mode. Instead of granting all capabilities, add only what `dockerd` needs:
|
|
191
|
+
|
|
192
|
+
```yaml
|
|
193
|
+
runtime:
|
|
194
|
+
type: docker
|
|
195
|
+
docker:
|
|
196
|
+
image: superkick/agent-dind:latest
|
|
197
|
+
cap_add:
|
|
198
|
+
- SYS_ADMIN
|
|
199
|
+
- NET_ADMIN
|
|
200
|
+
- MKNOD
|
|
201
|
+
- SYS_PTRACE
|
|
202
|
+
security_opt:
|
|
203
|
+
- seccomp:unconfined
|
|
204
|
+
- apparmor:unconfined
|
|
205
|
+
volumes:
|
|
206
|
+
- superkick-dind-storage:/var/lib/docker
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
The exact capabilities needed depend on your kernel version, storage driver, and Docker version. `SYS_ADMIN` is the essential one (for mount namespaces); the others handle networking and device creation. If the inner daemon fails to start, check `dockerd` logs for the specific denied operation and add the corresponding capability.
|
|
210
|
+
|
|
211
|
+
**Pros:** Narrower privilege surface than `privileged: true`. Same isolation and cleanup benefits as full DinD.
|
|
212
|
+
|
|
213
|
+
**Cons:** More fragile — the required capability set varies across environments. Still requires `SYS_ADMIN`, which is itself a broad capability. `seccomp:unconfined` disables the default seccomp profile.
|
|
214
|
+
|
|
215
|
+
**When to use:** When you want DinD isolation but your security policy prohibits `privileged: true`.
|
|
216
|
+
|
|
217
|
+
### Option 4: Sysbox runtime (unprivileged DinD)
|
|
218
|
+
|
|
219
|
+
[Sysbox](https://github.com/nestybox/sysbox) is a purpose-built container runtime (acquired by Docker Inc. in 2022) that enables Docker-in-Docker without any elevated privileges. It uses Linux user namespaces to ensure the container's root user has zero capabilities on the host.
|
|
220
|
+
|
|
221
|
+
Sysbox is installed as an alternative OCI runtime on the Docker host. Use the `container_runtime` setting to tell Docker to run agent containers with Sysbox — no privileged mode, no capabilities, no security opt-outs needed:
|
|
222
|
+
|
|
223
|
+
```yaml
|
|
224
|
+
runtime:
|
|
225
|
+
type: docker
|
|
226
|
+
docker:
|
|
227
|
+
image: superkick/agent-dind:latest
|
|
228
|
+
container_runtime: sysbox-runc
|
|
229
|
+
# No privileged, no cap_add, no security_opt needed
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Sysbox must be [installed on the Docker host](https://github.com/nestybox/sysbox#installation). Once installed, the `container_runtime: sysbox-runc` setting routes agent containers through it automatically — no changes to the host daemon's default runtime are required.
|
|
233
|
+
|
|
234
|
+
**Pros:** No elevated privileges at all. Full DinD isolation. Automatic cleanup. The most secure option for running nested containers.
|
|
235
|
+
|
|
236
|
+
**Cons:** Requires Sysbox to be installed on the Docker host (not available in all environments). Not supported on all Linux distributions or kernel versions.
|
|
237
|
+
|
|
238
|
+
**When to use:** Production environments where security is a priority and you control the host runtime configuration.
|
|
239
|
+
|
|
240
|
+
### Storage driver considerations
|
|
241
|
+
|
|
242
|
+
When running a Docker daemon inside a container, the inner daemon needs a storage driver for its image and container layers. The default `overlay2` driver works when the outer filesystem also supports it, but **overlay-on-overlay** (nested OverlayFS) can cause performance issues and subtle bugs on some kernels.
|
|
243
|
+
|
|
244
|
+
Best practices:
|
|
245
|
+
- **Use a volume mount** for `/var/lib/docker` — this gives the inner daemon a direct filesystem (typically ext4 or xfs) instead of nesting overlay on overlay
|
|
246
|
+
- **`vfs` as a fallback** — the `vfs` storage driver works everywhere but copies full layers (no copy-on-write), so it uses more disk and is slower. Set it via `dockerd --storage-driver=vfs` in your entrypoint if you hit overlay issues
|
|
247
|
+
- **tmpfs for ephemeral workloads** — if agents don't need to persist Docker images across runs, mount `/var/lib/docker` as tmpfs for maximum performance
|
|
248
|
+
|
|
249
|
+
### Comparison
|
|
250
|
+
|
|
251
|
+
| Approach | Isolation | Cleanup | Security | Setup complexity |
|
|
252
|
+
|----------|-----------|---------|----------|-----------------|
|
|
253
|
+
| Socket passthrough | None — shared daemon | Manual | Agent has full host Docker access | Low |
|
|
254
|
+
| DinD (privileged) | Full | Automatic | `privileged` grants broad host access | Low |
|
|
255
|
+
| DinD (targeted caps) | Full | Automatic | Narrower but still requires `SYS_ADMIN` | Medium |
|
|
256
|
+
| Sysbox | Full | Automatic | No elevated privileges | Medium (host setup) |
|