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.
Files changed (199) hide show
  1. checksums.yaml +7 -0
  2. data/CLA.md +91 -0
  3. data/CLAUDE.md +2226 -0
  4. data/CONTRIBUTING.md +104 -0
  5. data/LICENSE +108 -0
  6. data/LICENSE-COMMERCIAL.md +39 -0
  7. data/PLAN.md +161 -0
  8. data/README.md +1155 -0
  9. data/exe/superkick +6 -0
  10. data/lib/superkick/agent/runtime.rb +82 -0
  11. data/lib/superkick/agent/runtimes/local.rb +74 -0
  12. data/lib/superkick/agent/runtimes.rb +4 -0
  13. data/lib/superkick/agent.rb +209 -0
  14. data/lib/superkick/agent_store.rb +85 -0
  15. data/lib/superkick/attach/client.rb +245 -0
  16. data/lib/superkick/attach/protocol.rb +71 -0
  17. data/lib/superkick/attach/server.rb +371 -0
  18. data/lib/superkick/budget_checker.rb +120 -0
  19. data/lib/superkick/buffer/client.rb +91 -0
  20. data/lib/superkick/buffer/server.rb +127 -0
  21. data/lib/superkick/cli/agent.rb +524 -0
  22. data/lib/superkick/cli/completion.rb +591 -0
  23. data/lib/superkick/cli/goal.rb +71 -0
  24. data/lib/superkick/cli/mcp.rb +34 -0
  25. data/lib/superkick/cli/monitor.rb +47 -0
  26. data/lib/superkick/cli/notifier.rb +39 -0
  27. data/lib/superkick/cli/repository.rb +46 -0
  28. data/lib/superkick/cli/server.rb +106 -0
  29. data/lib/superkick/cli/setup.rb +166 -0
  30. data/lib/superkick/cli/spawner.rb +85 -0
  31. data/lib/superkick/cli/team.rb +407 -0
  32. data/lib/superkick/cli.rb +175 -0
  33. data/lib/superkick/client_registry.rb +30 -0
  34. data/lib/superkick/configuration.rb +178 -0
  35. data/lib/superkick/connection.rb +56 -0
  36. data/lib/superkick/control/client.rb +78 -0
  37. data/lib/superkick/control/reply.rb +43 -0
  38. data/lib/superkick/control/server.rb +1271 -0
  39. data/lib/superkick/cost_accumulator.rb +53 -0
  40. data/lib/superkick/cost_extractor.rb +65 -0
  41. data/lib/superkick/cost_poller.rb +70 -0
  42. data/lib/superkick/driver/profile_source.rb +134 -0
  43. data/lib/superkick/driver.rb +179 -0
  44. data/lib/superkick/drivers/claude_code.rb +110 -0
  45. data/lib/superkick/drivers/codex.rb +57 -0
  46. data/lib/superkick/drivers/copilot.rb +75 -0
  47. data/lib/superkick/drivers/gemini.rb +86 -0
  48. data/lib/superkick/drivers/goose.rb +74 -0
  49. data/lib/superkick/drivers.rb +16 -0
  50. data/lib/superkick/drop.rb +80 -0
  51. data/lib/superkick/drops.rb +76 -0
  52. data/lib/superkick/environment_executor.rb +90 -0
  53. data/lib/superkick/goal.rb +95 -0
  54. data/lib/superkick/goals/agent_exit.rb +41 -0
  55. data/lib/superkick/goals/agent_signal.rb +42 -0
  56. data/lib/superkick/goals/command.rb +103 -0
  57. data/lib/superkick/history_buffer.rb +38 -0
  58. data/lib/superkick/hosted/attach/bridge.rb +52 -0
  59. data/lib/superkick/hosted/attach/client.rb +208 -0
  60. data/lib/superkick/hosted/attach/relay.rb +313 -0
  61. data/lib/superkick/hosted/attach/relay_store.rb +48 -0
  62. data/lib/superkick/hosted/bridge.rb +263 -0
  63. data/lib/superkick/hosted/buffer/bridge.rb +42 -0
  64. data/lib/superkick/hosted/buffer/client.rb +63 -0
  65. data/lib/superkick/hosted/buffer/relay.rb +126 -0
  66. data/lib/superkick/hosted/buffer/relay_store.rb +42 -0
  67. data/lib/superkick/hosted/control/client.rb +84 -0
  68. data/lib/superkick/hosted/mcp_proxy.rb +144 -0
  69. data/lib/superkick/inject_handler.rb +24 -0
  70. data/lib/superkick/injection_guard.rb +26 -0
  71. data/lib/superkick/injection_queue.rb +177 -0
  72. data/lib/superkick/injector.rb +65 -0
  73. data/lib/superkick/input_buffer.rb +171 -0
  74. data/lib/superkick/integrations/bugsnag/README.md +98 -0
  75. data/lib/superkick/integrations/bugsnag/spawner.rb +307 -0
  76. data/lib/superkick/integrations/bugsnag/templates/error_opened.liquid +17 -0
  77. data/lib/superkick/integrations/bugsnag.rb +7 -0
  78. data/lib/superkick/integrations/circleci/README.md +75 -0
  79. data/lib/superkick/integrations/circleci/monitor.rb +185 -0
  80. data/lib/superkick/integrations/circleci/probe.rb +36 -0
  81. data/lib/superkick/integrations/circleci/templates/ci_failure.liquid +8 -0
  82. data/lib/superkick/integrations/circleci/templates/ci_success.liquid +1 -0
  83. data/lib/superkick/integrations/circleci.rb +8 -0
  84. data/lib/superkick/integrations/datadog/README.md +253 -0
  85. data/lib/superkick/integrations/datadog/alert_goal.rb +94 -0
  86. data/lib/superkick/integrations/datadog/alert_monitor.rb +163 -0
  87. data/lib/superkick/integrations/datadog/alert_spawner.rb +201 -0
  88. data/lib/superkick/integrations/datadog/notification_templates/default.liquid +10 -0
  89. data/lib/superkick/integrations/datadog/notifier.rb +294 -0
  90. data/lib/superkick/integrations/datadog/spawner.rb +201 -0
  91. data/lib/superkick/integrations/datadog/templates/alert_changed.liquid +8 -0
  92. data/lib/superkick/integrations/datadog/templates/alert_escalated.liquid +8 -0
  93. data/lib/superkick/integrations/datadog/templates/alert_recovered.liquid +14 -0
  94. data/lib/superkick/integrations/datadog/templates/alert_triggered.liquid +29 -0
  95. data/lib/superkick/integrations/datadog/templates/error_opened.liquid +15 -0
  96. data/lib/superkick/integrations/datadog.rb +14 -0
  97. data/lib/superkick/integrations/docker/README.md +256 -0
  98. data/lib/superkick/integrations/docker/client.rb +295 -0
  99. data/lib/superkick/integrations/docker/runtime.rb +218 -0
  100. data/lib/superkick/integrations/docker.rb +4 -0
  101. data/lib/superkick/integrations/git/repository_source.rb +66 -0
  102. data/lib/superkick/integrations/git/version_control.rb +119 -0
  103. data/lib/superkick/integrations/git.rb +8 -0
  104. data/lib/superkick/integrations/github/README.md +300 -0
  105. data/lib/superkick/integrations/github/check_failed_spawner.rb +199 -0
  106. data/lib/superkick/integrations/github/drops.rb +114 -0
  107. data/lib/superkick/integrations/github/goal.rb +135 -0
  108. data/lib/superkick/integrations/github/issue_goal.rb +104 -0
  109. data/lib/superkick/integrations/github/issue_spawner.rb +160 -0
  110. data/lib/superkick/integrations/github/monitor.rb +251 -0
  111. data/lib/superkick/integrations/github/probe.rb +30 -0
  112. data/lib/superkick/integrations/github/repository_source.rb +228 -0
  113. data/lib/superkick/integrations/github/templates/check_failed.liquid +10 -0
  114. data/lib/superkick/integrations/github/templates/ci_failure.liquid +5 -0
  115. data/lib/superkick/integrations/github/templates/ci_success.liquid +1 -0
  116. data/lib/superkick/integrations/github/templates/issue_opened.liquid +20 -0
  117. data/lib/superkick/integrations/github/templates/pr_comment.liquid +2 -0
  118. data/lib/superkick/integrations/github/templates/pr_review.liquid +4 -0
  119. data/lib/superkick/integrations/github.rb +16 -0
  120. data/lib/superkick/integrations/honeybadger/README.md +97 -0
  121. data/lib/superkick/integrations/honeybadger/notification_templates/default.liquid +8 -0
  122. data/lib/superkick/integrations/honeybadger/notifier.rb +250 -0
  123. data/lib/superkick/integrations/honeybadger/spawner.rb +214 -0
  124. data/lib/superkick/integrations/honeybadger/templates/error_opened.liquid +17 -0
  125. data/lib/superkick/integrations/honeybadger.rb +9 -0
  126. data/lib/superkick/integrations/shell/README.md +83 -0
  127. data/lib/superkick/integrations/shell/monitor.rb +87 -0
  128. data/lib/superkick/integrations/shell/templates/shell_alert.liquid +6 -0
  129. data/lib/superkick/integrations/shell/templates/shell_success.liquid +6 -0
  130. data/lib/superkick/integrations/shell.rb +7 -0
  131. data/lib/superkick/integrations/shortcut/README.md +193 -0
  132. data/lib/superkick/integrations/shortcut/drops.rb +91 -0
  133. data/lib/superkick/integrations/shortcut/monitor.rb +582 -0
  134. data/lib/superkick/integrations/shortcut/probe.rb +34 -0
  135. data/lib/superkick/integrations/shortcut/spawner.rb +264 -0
  136. data/lib/superkick/integrations/shortcut/templates/related_story_changed.liquid +6 -0
  137. data/lib/superkick/integrations/shortcut/templates/story_blocker.liquid +8 -0
  138. data/lib/superkick/integrations/shortcut/templates/story_comment.liquid +5 -0
  139. data/lib/superkick/integrations/shortcut/templates/story_description_changed.liquid +19 -0
  140. data/lib/superkick/integrations/shortcut/templates/story_owner_changed.liquid +10 -0
  141. data/lib/superkick/integrations/shortcut/templates/story_ready.liquid +41 -0
  142. data/lib/superkick/integrations/shortcut/templates/story_state_changed.liquid +9 -0
  143. data/lib/superkick/integrations/shortcut/templates/story_unblocked.liquid +5 -0
  144. data/lib/superkick/integrations/shortcut.rb +11 -0
  145. data/lib/superkick/integrations/slack/README.md +297 -0
  146. data/lib/superkick/integrations/slack/drops.rb +70 -0
  147. data/lib/superkick/integrations/slack/notifier.rb +426 -0
  148. data/lib/superkick/integrations/slack/spawner.rb +251 -0
  149. data/lib/superkick/integrations/slack/templates/default.liquid +17 -0
  150. data/lib/superkick/integrations/slack/templates/slack_reply.liquid +3 -0
  151. data/lib/superkick/integrations/slack/templates/spawn/slack_message.liquid +10 -0
  152. data/lib/superkick/integrations/slack/thread_monitor.rb +161 -0
  153. data/lib/superkick/integrations/slack.rb +12 -0
  154. data/lib/superkick/liquid.rb +129 -0
  155. data/lib/superkick/local/repository_source.rb +148 -0
  156. data/lib/superkick/mcp_server.rb +596 -0
  157. data/lib/superkick/monitor.rb +215 -0
  158. data/lib/superkick/notification_dispatcher.rb +280 -0
  159. data/lib/superkick/notifier.rb +173 -0
  160. data/lib/superkick/notifier_state_store.rb +55 -0
  161. data/lib/superkick/notifier_template.rb +121 -0
  162. data/lib/superkick/notifiers/command.rb +124 -0
  163. data/lib/superkick/notifiers/terminal_bell.rb +41 -0
  164. data/lib/superkick/output_logger.rb +54 -0
  165. data/lib/superkick/poller.rb +126 -0
  166. data/lib/superkick/process_runner.rb +87 -0
  167. data/lib/superkick/pty_proxy.rb +403 -0
  168. data/lib/superkick/registry.rb +75 -0
  169. data/lib/superkick/repository_source.rb +195 -0
  170. data/lib/superkick/server.rb +211 -0
  171. data/lib/superkick/session_recorder.rb +154 -0
  172. data/lib/superkick/setup.rb +160 -0
  173. data/lib/superkick/spawn/agent_spawner.rb +311 -0
  174. data/lib/superkick/spawn/approval_store.rb +113 -0
  175. data/lib/superkick/spawn/handler.rb +144 -0
  176. data/lib/superkick/spawn/injector.rb +119 -0
  177. data/lib/superkick/spawn/workflow_executor.rb +196 -0
  178. data/lib/superkick/spawn/workflow_validator.rb +77 -0
  179. data/lib/superkick/spawner.rb +67 -0
  180. data/lib/superkick/supervisor.rb +516 -0
  181. data/lib/superkick/team/artifact_store.rb +92 -0
  182. data/lib/superkick/team/log.rb +140 -0
  183. data/lib/superkick/team/log_entry_drop.rb +34 -0
  184. data/lib/superkick/team/log_monitor.rb +84 -0
  185. data/lib/superkick/team/log_notifier.rb +96 -0
  186. data/lib/superkick/team/log_store.rb +40 -0
  187. data/lib/superkick/template_filters.rb +24 -0
  188. data/lib/superkick/template_renderer.rb +223 -0
  189. data/lib/superkick/templates/team_log/planning_agent.liquid +38 -0
  190. data/lib/superkick/templates/team_log/team_digest.liquid +45 -0
  191. data/lib/superkick/templates/team_log/teammate_message.liquid +7 -0
  192. data/lib/superkick/templates/team_log/worker_kickoff.liquid +37 -0
  193. data/lib/superkick/templates/workflow/workflow_triggered.liquid +22 -0
  194. data/lib/superkick/version.rb +5 -0
  195. data/lib/superkick/version_control.rb +135 -0
  196. data/lib/superkick/yaml_config.rb +302 -0
  197. data/lib/superkick.rb +198 -0
  198. data/plan.md +267 -0
  199. 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) |