Documentation
¶
Overview ¶
Package agentops turns a structured agent/tool error into a filed GitHub ticket and (optionally) tees up a fix PR via a coding agent.
The single entrypoint is Capture, which:
- Derives a stable fingerprint for the error (or uses an override).
- Dedupes against existing issues so a recurring error re-opens / comments on the original ticket instead of spamming duplicates.
- Files a bug issue from a template (title, repro, observed error, environment, labels "auto-filed" + the platform).
- Optionally triggers a fix pipeline (a Cursor Cloud agent, a local containerized executor, or any custom Trigger) that branches and opens a PR against an up-to-date pull of the target repo.
The library is importable across services. GitHub filing defaults to the `gh` CLI (GHFiler) and the pipeline defaults to a no-op (NoopTrigger); both are interfaces so callers can swap in their own backends.
Usage:
cap, err := agentops.New(agentops.Config{Repo: "teslashibe/smore"})
if err != nil { /* misconfigured */ }
res, err := cap.Capture(ctx, agentops.ErrorEvent{
Tool: "themls_get_json",
Platform: "themls",
Code: "internal_error",
Message: "themls: not found",
})
All operations are best-effort from the caller's perspective: Capture returns an error only when it cannot file at all. A failed pipeline trigger never fails the capture — it is reported via Result.PipelineErr.
Index ¶
- Variables
- func Fingerprint(ev ErrorEvent) string
- type Action
- type Capturer
- type CommandTrigger
- type Config
- type CursorCloudTrigger
- type ErrorEvent
- type Filer
- type GHFiler
- func (g *GHFiler) Comment(ctx context.Context, repo string, number int, body string) error
- func (g *GHFiler) Create(ctx context.Context, repo string, spec IssueSpec) (*Issue, error)
- func (g *GHFiler) Find(ctx context.Context, repo, fingerprint string) (*Issue, error)
- func (g *GHFiler) Reopen(ctx context.Context, repo string, number int) error
- type Issue
- type IssueSpec
- type NoopTrigger
- type RESTFiler
- func (f *RESTFiler) Comment(ctx context.Context, repo string, number int, body string) error
- func (f *RESTFiler) Create(ctx context.Context, repo string, spec IssueSpec) (*Issue, error)
- func (f *RESTFiler) Find(ctx context.Context, repo, fingerprint string) (*Issue, error)
- func (f *RESTFiler) Reopen(ctx context.Context, repo string, number int) error
- type Request
- type RequestKind
- type Result
- type Trigger
- type TriggerResult
- type TriggerSpec
Constants ¶
This section is empty.
Variables ¶
var DefaultLabels = []string{"auto-filed", "bug"}
DefaultLabels are applied to every filed ticket.
Functions ¶
func Fingerprint ¶
func Fingerprint(ev ErrorEvent) string
Fingerprint returns the dedupe key for an event: the explicit override when set, otherwise a 16-hex-char digest of platform/tool/code/normalized message. Exported so callers (and tests) can precompute it.
Types ¶
type Action ¶
type Action string
Action describes what Capture did with the event.
const ( // ActionCreated means a new issue was filed. ActionCreated Action = "created" // ActionDeduped means an existing open issue was found and commented on. ActionDeduped Action = "deduped" // ActionReopened means a closed issue matching the fingerprint was // re-opened and commented on. ActionReopened Action = "reopened" )
type Capturer ¶
type Capturer struct {
// contains filtered or unexported fields
}
Capturer files tickets and optionally triggers fix pipelines. Construct with New; safe for concurrent use as long as the Filer/Trigger are.
func (*Capturer) Capture ¶
Capture files (or dedupes onto) a GitHub ticket for ev and, when the pipeline is enabled, triggers a fix run. It returns an error only when the ticket cannot be filed; a failed pipeline trigger is reported via Result.PipelineErr.
func (*Capturer) CaptureRequest ¶ added in v0.3.0
CaptureRequest files (or dedupes onto) a GitHub ticket for a user-submitted bug report or feature request, then — when the pipeline is enabled — dispatches a triage agent to enrich the ticket with codebase context (and, for bugs, open a fix PR). Filing is deterministic and deduped; the agent only ever enriches the already-filed issue. Returns an error only when the ticket cannot be filed.
type CommandTrigger ¶
type CommandTrigger struct {
// Name is the binary to run (e.g. "agentops-runner", "cursor-agent").
Name string
// Args are static arguments prepended to the invocation.
Args []string
// Background runs the command detached (fire-and-forget) when true.
// When false, Trigger blocks until the command exits.
Background bool
// contains filtered or unexported fields
}
CommandTrigger launches the fix pipeline by executing an arbitrary command, e.g. the containerized agentops-runner or a `cursor-agent` CLI. The trigger spec is exported to the child process as environment variables:
AGENTOPS_REPO, AGENTOPS_ISSUE_NUMBER, AGENTOPS_ISSUE_URL, AGENTOPS_FINGERPRINT, AGENTOPS_TITLE
and the prompt is piped to the command's stdin.
func (*CommandTrigger) Trigger ¶
func (t *CommandTrigger) Trigger(ctx context.Context, spec TriggerSpec) (TriggerResult, error)
type Config ¶
type Config struct {
// Repo is the default target repository ("owner/name"). Required unless
// every ErrorEvent sets Repo.
Repo string
// Filer is the GitHub backend. Defaults to NewGHFiler() (the `gh` CLI).
Filer Filer
// Trigger is the fix pipeline. Defaults to NoopTrigger{} (file only).
Trigger Trigger
// Labels override DefaultLabels when non-nil (the platform label is
// always added on top).
Labels []string
// EnablePipeline gates the fix pipeline. When false (default) Capture
// only files/dedupes tickets and never triggers Trigger. This is the
// "behind a config flag" switch the integration shim toggles.
EnablePipeline bool
// RedactBytes caps the rendered tool-input size in the ticket body.
RedactBytes int
// DedupeTTL is how long a freshly filed fingerprint is remembered
// in-process so rapid recurrences dedupe even before GitHub search
// indexes the issue. Defaults to 5m.
DedupeTTL time.Duration
// Now overrides the clock (tests). Defaults to time.Now.
Now func() time.Time
}
Config configures a Capturer.
type CursorCloudTrigger ¶
type CursorCloudTrigger struct {
// APIKey is the Cursor Cloud Agents API key. Required.
APIKey string
// BaseURL overrides the API host (default https://api.cursor.com).
BaseURL string
// Model is the agent model id (e.g. "composer-2"). Empty lets Cursor
// choose its default.
Model string
// StartingRef is the branch the agent forks from (default "main").
StartingRef string
// AutoCreatePR asks the agent to open a PR when done (default true).
AutoCreatePR bool
// UsePrivateWorker routes the run to a self-hosted worker pool.
UsePrivateWorker bool
// HTTPClient overrides the default client (10s connect, no global
// timeout so long runs aren't cut — context governs cancellation).
HTTPClient *http.Client
// RepoURL maps "owner/name" → full clone URL. Empty defaults to GitHub
// https URLs.
RepoURLFunc func(repo string) string
// AuthScheme selects the Authorization header style: "basic" (default,
// matches the documented v1 `-u KEY:` curl examples) or "bearer".
AuthScheme string
}
CursorCloudTrigger launches a Cursor Cloud agent against the target repo via the Cloud Agents API. The agent clones the repo at startingRef, works the prompt, and (with AutoCreatePR) opens a PR.
Auth uses a Cursor Cloud Agents API key (Bearer). Note: the cloud-agents endpoint requires an agent-specific key, not a generic dashboard key.
func (*CursorCloudTrigger) Trigger ¶
func (c *CursorCloudTrigger) Trigger(ctx context.Context, spec TriggerSpec) (TriggerResult, error)
Trigger creates a Cloud Agent and returns its id/url as the run ref.
type ErrorEvent ¶
type ErrorEvent struct {
// Tool is the failing tool name, e.g. "themls_get_json".
Tool string `json:"tool"`
// Platform is the connected platform / integration, e.g. "themls".
Platform string `json:"platform"`
// Code is the machine error code, e.g. "internal_error".
Code string `json:"code"`
// Message is the human-readable error message.
Message string `json:"message"`
// Input is the (redacted) tool input that produced the error. Sensitive
// keys are stripped by Capture before the value is written to a ticket.
Input json.RawMessage `json:"input,omitempty"`
// SessionID correlates the error to an agent session.
SessionID string `json:"session_id,omitempty"`
// Stack is an optional stack trace or diagnostic context.
Stack string `json:"stack,omitempty"`
// Labels are extra GitHub labels applied in addition to the defaults
// ("auto-filed" and "platform:<Platform>").
Labels []string `json:"labels,omitempty"`
// Fingerprint overrides the derived dedupe key. Leave empty to derive
// one from Platform/Tool/Code/normalized Message.
Fingerprint string `json:"fingerprint,omitempty"`
// Repo optionally overrides Config.Repo for this event ("owner/name").
Repo string `json:"repo,omitempty"`
// Env carries environment metadata rendered into the ticket's
// Environment section (e.g. "app_version", "commit", "stack").
Env map[string]string `json:"env,omitempty"`
// OccurredAt is when the error happened. Zero means "now".
OccurredAt time.Time `json:"occurred_at,omitempty"`
}
ErrorEvent is the structured tool-error contract shared across services (the same schema feeds the in-app feedback tools). Tool, Platform, Code and Message are the minimum required to file a useful ticket; everything else is optional enrichment.
type Filer ¶
type Filer interface {
// Find returns the most recent issue (any state) whose body carries the
// fingerprint marker, or nil when none exists.
Find(ctx context.Context, repo, fingerprint string) (*Issue, error)
// Create files a new issue and returns it.
Create(ctx context.Context, repo string, spec IssueSpec) (*Issue, error)
// Comment adds a comment to an existing issue.
Comment(ctx context.Context, repo string, number int, body string) error
// Reopen transitions a closed issue back to open. A no-op on open issues.
Reopen(ctx context.Context, repo string, number int) error
}
Filer abstracts the GitHub backend so callers can swap the default `gh` CLI implementation for the REST API, an in-memory fake (tests), or a queue.
type GHFiler ¶
type GHFiler struct {
// Bin is the gh binary name/path. Empty defaults to "gh".
Bin string
// contains filtered or unexported fields
}
GHFiler files issues through the GitHub CLI (`gh`). It requires `gh` on PATH and an authenticated session (GH_TOKEN / `gh auth login`).
func NewGHFiler ¶
func NewGHFiler() *GHFiler
NewGHFiler returns a GHFiler using the `gh` binary on PATH.
func (*GHFiler) Create ¶
Create files a new issue. Labels are best-effort: gh fails the call if a label doesn't exist, so missing labels are created on demand first.
func (*GHFiler) Find ¶
Find searches issues (any state) for the fingerprint marker embedded in the body, then confirms the exact marker is present before treating it as a match. The confirmation step is essential: `gh --search` is tokenized full-text, so it can surface issues that merely contain similar tokens; a false positive would silently merge an unrelated bug onto the wrong ticket.
type Issue ¶
type Issue struct {
Number int `json:"number"`
URL string `json:"url"`
State string `json:"state"` // "open" or "closed" (lowercased)
Title string `json:"title"`
}
Issue is the minimal view of a GitHub issue the dedupe logic needs.
type NoopTrigger ¶
type NoopTrigger struct{}
NoopTrigger does nothing. It is the default so Capture only files tickets unless a real pipeline is wired.
func (NoopTrigger) Trigger ¶
func (NoopTrigger) Trigger(context.Context, TriggerSpec) (TriggerResult, error)
type RESTFiler ¶ added in v0.2.0
type RESTFiler struct {
// Token is the GitHub token. Required.
Token string
// BaseURL overrides the API host (e.g. GitHub Enterprise). Defaults to
// https://api.github.com.
BaseURL string
// HTTPClient overrides the default client (10s timeout).
HTTPClient httpDoer
}
RESTFiler files issues through the GitHub REST API using a token — no `gh` CLI or git binary required, so it works inside a minimal container image. This is the recommended backend for server deployments.
The token needs Issues read/write on the target repo (fine-grained PAT: "Issues" read & write; classic PAT: `repo`). A service-account token is ideal so filings are attributed to a bot.
func NewRESTFiler ¶ added in v0.2.0
NewRESTFiler returns a RESTFiler for the public GitHub API.
type Request ¶ added in v0.3.0
type Request struct {
// Kind is "bug" or "feature". Required.
Kind RequestKind `json:"kind"`
// Title is an optional short summary. Derived from Body when empty.
Title string `json:"title,omitempty"`
// Body is the user's request text. Required.
Body string `json:"body"`
// Reporter identifies who submitted it (email / user id), for attribution.
Reporter string `json:"reporter,omitempty"`
// SessionID correlates to the originating agent session.
SessionID string `json:"session_id,omitempty"`
// Labels are extra GitHub labels (defaults + kind + needs-triage added).
Labels []string `json:"labels,omitempty"`
// Fingerprint overrides the derived dedupe key.
Fingerprint string `json:"fingerprint,omitempty"`
// Repo optionally overrides Config.Repo for this request ("owner/name").
Repo string `json:"repo,omitempty"`
// Env carries metadata rendered into the ticket (e.g. app_version).
Env map[string]string `json:"env,omitempty"`
// OccurredAt is when it was submitted. Zero means now.
OccurredAt time.Time `json:"occurred_at,omitempty"`
}
Request is a free-text user bug report or feature request. Unlike ErrorEvent (structured, machine-emitted), a Request is human prose that a triage agent turns into a code-aware ticket.
type RequestKind ¶ added in v0.3.0
type RequestKind string
RequestKind classifies a user-submitted request.
const ( // KindBug is a bug report; triage enriches the ticket and opens a fix PR. KindBug RequestKind = "bug" // KindFeature is a feature request; triage enriches the ticket only. KindFeature RequestKind = "feature" )
type Result ¶
type Result struct {
// Fingerprint is the dedupe key used for this event.
Fingerprint string `json:"fingerprint"`
// Action is what happened to the ticket (created/deduped/reopened).
Action Action `json:"action"`
// IssueNumber and IssueURL identify the filed/matched issue.
IssueNumber int `json:"issue_number"`
IssueURL string `json:"issue_url"`
// PipelineTriggered reports whether a fix pipeline was kicked off.
PipelineTriggered bool `json:"pipeline_triggered"`
// PipelineRef is an opaque handle to the triggered run (e.g. a Cursor
// Cloud agent ID or a PR URL), when available.
PipelineRef string `json:"pipeline_ref,omitempty"`
// PipelineErr holds a non-fatal trigger error. A failed trigger never
// fails Capture; the ticket is still filed.
PipelineErr string `json:"pipeline_err,omitempty"`
}
Result is the outcome of a Capture call.
type Trigger ¶
type Trigger interface {
Trigger(ctx context.Context, spec TriggerSpec) (TriggerResult, error)
}
Trigger kicks off the fix pipeline for a filed ticket. Implementations should be non-blocking where possible (enqueue and return a handle) and must never panic; Capture treats trigger errors as non-fatal.
type TriggerResult ¶
type TriggerResult struct {
Triggered bool
// Ref is an opaque handle to the run (Cursor Cloud agent ID, PR URL, …).
Ref string
}
TriggerResult reports the outcome of a pipeline trigger.
type TriggerSpec ¶
type TriggerSpec struct {
Repo string // "owner/name"
IssueNumber int
IssueURL string
Fingerprint string
Title string
// Kind classifies the run: "error" (automated tool-error fix), "bug" or
// "feature" (user request triage/enrich). Implementations may use it for
// routing/labels; it is also exported to CommandTrigger child env.
Kind string
// OpenPR requests that the agent open a PR (bugs/errors) vs. enrich-only
// (features). Drives Cursor Cloud's autoCreatePR per call.
OpenPR bool
// Prompt is a ready-to-run instruction for the agent. Built by the
// capturer from the event/request.
Prompt string
}
TriggerSpec is the payload handed to a pipeline once a ticket is filed.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
agentops-runner
command
Command agentops-runner is the containerized fix executor for the agentops-go pipeline.
|
Command agentops-runner is the containerized fix executor for the agentops-go pipeline. |
|
examples
|
|
|
quickstart
command
Quickstart: capture a tool error into a GitHub ticket.
|
Quickstart: capture a tool error into a GitHub ticket. |