Skip to content

Progressive GitHub Subscription with Event Tracking#40

Open
JaredforReal wants to merge 4 commits into
mainfrom
layer
Open

Progressive GitHub Subscription with Event Tracking#40
JaredforReal wants to merge 4 commits into
mainfrom
layer

Conversation

@JaredforReal
Copy link
Copy Markdown
Owner

Problem

The GitHub adaptor treats all issues and PRs equally. Every item fetched via the Issues API creates an envelope in the inbox. For active repositories (e.g., vllm-project/vllm with hundreds of daily updates), this floods the inbox with noise. Key gaps:

  1. No granularity: Users can't choose which event types to receive (releases, new PRs, new issues)
  2. No per-item tracking: Updates on interesting items are silently dropped by seen_ids dedup
  3. No comment capture: The adaptor fetches issue/PR metadata but never reads comments
  4. No releases: GitHub Releases are not supported at all

Proposed Solution

Introduce an Event entity separate from Envelope, implementing a progressive subscription model:

  • Envelope = single incoming notification (inbox, one-shot processing)
  • Event = tracked item that accumulates updates in one view with a persistent agent session

Event Type Labels

GitHub envelopes are tagged with event type labels for policy-based routing:

Label Meaning
github:release New version release
github:new_pr Newly opened pull request
github:new_issue Newly opened issue
github:update Update on a tracked item (comment, status change)

These use the existing labels field. The policy engine already matches on labels, so no policy engine changes are needed.

Subscription Flow

GitHub ──fetch──> Envelope (Inbox)
                      │
            ┌─────────┼──────────┐
            ▼         ▼          ▼
        Dismiss    Process     Track
            │      (agent)       │
            ▼         │          ▼
          DONE     IN_REVIEW   Create Event (Tracked view)
                       │          │
                       ▼          ▼
                     DONE      Agent analyzes
                                 │
                              New comments arrive
                                 │
                                 ▼
                              Appended to Event.updates
                              "2 new updates" badge
                                 │
                              User clicks "Re-analyze"
                                 │
                                 ▼
                              Agent resumes same session
                                 │
                              User clicks "Resolve"
                                 │
                                 ▼
                              RESOLVED (Done)

Data Model

Envelope Changes

Add TRACKED status to EnvelopeStatus:

class EnvelopeStatus(StrEnum):
    PENDING = "pending"
    PROCESSING = "processing"
    IN_REVIEW = "in_review"
    DONE = "done"
    DISMISSED = "dismissed"
    FAILED = "failed"
    TRACKED = "tracked"  # NEW

When an envelope is tracked, its status becomes TRACKED and it leaves the inbox.

New events Table

CREATE TABLE events (
    id TEXT PRIMARY KEY,
    source_id TEXT UNIQUE,        -- "owner/repo#42"
    source TEXT,                  -- "github"
    title TEXT,
    "group" TEXT,
    envelope_id TEXT,             -- original envelope
    status TEXT DEFAULT 'active', -- "active" | "resolved"
    agent_session_id TEXT,
    agent_summary TEXT,
    labels TEXT DEFAULT '[]',
    metadata TEXT DEFAULT '{}',
    updates TEXT DEFAULT '[]',    -- JSON array of update objects
    created_at TEXT,
    updated_at TEXT
);

Update Object Shape

{
    "id": "comment:4567",
    "type": "comment",
    "author": "username",
    "body": "comment text...",
    "html_url": "https://github.com/...",
    "created_at": "2026-05-06T12:00:00Z",
    "seen": false
}

API Surface

Method Path Purpose
POST /api/envelopes/{id}/track Track envelope → creates Event
GET /api/events List events (active/resolved)
GET /api/events/{id} Get event with updates
POST /api/events/{id}/resolve Resolve event
POST /api/events/{id}/analyze Trigger agent re-analysis
PATCH /api/events/{id}/updates/seen Mark updates as seen

WebUI

Inbox View (existing, minor changes)

  • Event type badges on cards: release (green), new_pr/new_issue (blue)
  • "Track" button (eye icon) in ActionsPanel for GitHub envelopes

Tracked View (new)

  • Separate tab/panel showing active Events
  • Each card: title, source_id, "N new updates" badge, agent summary
  • Expandable event detail with update timeline
  • "Re-analyze" button (manual agent trigger)
  • "Resolve" button

Source Config

  • Events field gains "releases" option
  • Three checkboxes: Releases, New PRs, New Issues

GitHub Adaptor Changes

Event Type Labels

  • Issues tagged with github:new_issue
  • PRs tagged with github:new_pr
  • Releases tagged with github:release

Releases Support

  • New polling method: GET /repos/{owner}/{repo}/releases
  • Dedup by source_id (owner/repo:release:tag_name)
  • Tag-based cursor for incremental fetching

Comment Fetching for Tracked Items

  • Store injection: GitHubAdaptor.set_store(store)
  • Per poll cycle: fetch comments for each tracked item
  • Cursor: last_comment_id stored in Event metadata
  • On track: initialize cursor to latest existing comment (future-only)
  • Comments appended to Event.updates, not new envelopes

Rate Limit Budget

  • Issues/PRs: 1 API call per repo per cycle (unchanged)
  • Releases: 1 API call per repo per cycle
  • Comments: 1 API call per tracked item per cycle
  • With 20 tracked items @ 120s = ~600 req/hr, well within 5000/hr limit

Policy Examples

rules:
  - name: "Auto-summarize releases"
    match:
      source: github
      labels: ["github:release"]
    action:
      auto_approve: true
      priority: 1

  - name: "Critical issues"
    match:
      source: github
      labels: ["bug", "P0"]
    action:
      priority: 3
      auto_approve: false

  - name: "General GitHub"
    match:
      source: github
    action:
      priority: 1
      auto_approve: false

Implementation Phases

Phase Scope Est. Effort
1. Data model TRACKED status, events table, CRUD 2-3h
2. GitHub labels + releases Event type labels, releases polling 2-3h
3. Update fetching Comment polling for tracked items 2-3h
4. API endpoints Track/resolve/analyze/list 1-2h
5. WebUI Tracked view, track button, badges 3-4h
6. Agent integration Manual re-analysis flow 2-3h
7. Policies + prompts Bundled rules and templates 1h

Open Questions

  1. Status change detection: Should we detect open→closed/merged changes on tracked items and append them as updates? This requires re-fetching the issue metadata.
  2. Multi-source events: Could Events track items from non-GitHub sources (e.g., a Gmail thread)? The data model supports it but it's not in scope for this RFC.
  3. Event retention: How long should resolved Events be kept? Should there be a cleanup mechanism?
  4. Batch re-analysis: Should "Re-analyze" process all unseen updates at once, or individually?
  5. Notification for new updates: Should the WebUI show a badge/count for new updates on tracked items without polling?

Signed-off-by: JaredforReal <w13431838023@gmail.com>
Signed-off-by: JaredforReal <w13431838023@gmail.com>
Signed-off-by: JaredforReal <w13431838023@gmail.com>
Signed-off-by: JaredforReal <w13431838023@gmail.com>
Copilot AI review requested due to automatic review settings May 6, 2026 12:08
@JaredforReal JaredforReal reopened this May 6, 2026
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a “tracked events” concept alongside inbox Envelopes to support progressive GitHub subscriptions (new issues/PRs/releases as inbox items, with ongoing updates accumulated on tracked items), and adds a WebUI view + API endpoints to manage these tracked items.

Changes:

  • Added a new Event model persisted in a new events table, plus API endpoints to track/list/resolve/analyze events and mark updates as seen.
  • Extended the GitHub adaptor to label new issues/PRs, poll releases, and poll comments for tracked items (appending them as event updates).
  • Added a new “Tracked” WebUI view and supporting UI changes (status tracked, event-type badges, “Track” action).

Reviewed changes

Copilot reviewed 20 out of 24 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
loom/webui/frontend/src/pages/tracked/TrackedPanel.tsx New tracked-items panel (list + detail + updates timeline).
loom/webui/frontend/src/pages/config/ConfigFormEditor.tsx Adds releases to GitHub source “events” placeholder text.
loom/webui/frontend/src/lib/types.ts Adds tracked envelope status and new TrackedEvent/EventUpdate types.
loom/webui/frontend/src/lib/api.ts Adds client API helpers for tracking and event CRUD actions.
loom/webui/frontend/src/components/Sidebar.tsx Adds “Tracked” navigation entry.
loom/webui/frontend/src/components/KanbanBoard.tsx Adds tracked to status grouping.
loom/webui/frontend/src/components/EnvelopeDetail.tsx Hides github:* synthetic labels; shows event-type and tracked badges.
loom/webui/frontend/src/components/EnvelopeCard.tsx Displays event-type badge; hides github:* labels from label list.
loom/webui/frontend/src/components/Column.tsx Adds visual token styling for tracked status.
loom/webui/frontend/src/components/ActionsPanel.tsx Adds “Track” action for GitHub envelopes; treats tracked as terminal for dismiss logic.
loom/webui/frontend/src/App.tsx Adds tracked view routing to render TrackedPanel.
loom/state/store.py Introduces EventRow + event CRUD methods in the SQLite store.
loom/prompts/prompt_github_update.md New prompt template for tracked GitHub item updates.
loom/prompts/prompt_github_release.md New prompt template for GitHub release summaries.
loom/policies/github.yaml Adds a policy rule to auto-summarize GitHub releases.
loom/daemon.py Injects the store into the GitHub adaptor to enable tracked-item update polling.
loom/core/event.py Adds the Event dataclass model.
loom/core/envelope.py Adds TRACKED to EnvelopeStatus.
loom/api_server.py Adds event tracking/listing/resolution/analysis endpoints and event serialization.
loom/adaptor/github.py Adds release polling, event-type labels, and tracked comment polling/appending.
docs/setup/gmail-setup.md Adds a new Gmail setup guide (English).
docs/setup/gmail-setup-zh.md Adds a new Gmail setup guide (Chinese).
docs/setup/github-token.md Adds a GitHub token setup guide (English).
docs/setup/github-token-zh.md Adds a GitHub token setup guide (Chinese).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread loom/adaptor/github.py
Comment on lines +288 to +292
resp = await self._client.get(f"/repos/{key}/releases", params={"per_page": 30})
if resp.status_code == 304:
return
resp.raise_for_status()

Comment thread loom/adaptor/github.py
Comment on lines +297 to +306
release_cursor_key = f"release_cursor:{key}"
last_seen_tag = self._cursors.get(release_cursor_key)

for release in releases:
tag = release.get("tag_name", "")
if not tag:
continue
# Releases come newest-first; stop at cursor
if last_seen_tag and tag <= last_seen_tag:
break
Comment on lines 30 to 41
const grouped = useMemo(() => {
const g: Record<EnvelopeStatus, Envelope[]> = {
pending: [],
processing: [],
in_review: [],
done: [],
dismissed: [],
failed: [],
tracked: [],
}
for (const e of envelopes) g[e.status].push(e)
return g
Comment thread loom/api_server.py
Comment on lines +329 to +338
@app.patch("/api/events/{event_id}/updates/seen")
async def mark_updates_seen(event_id: str):
ctx = _ctx()
event = await ctx.store.get_event(event_id)
if event is None:
return JSONResponse(status_code=404, content={"error": "not found"})
for update in event.updates:
update["seen"] = True
await ctx.store.save_event(event)
return {"marked_seen": len(event.updates)}
Comment thread loom/api_server.py
Comment on lines +329 to +338
@app.patch("/api/events/{event_id}/updates/seen")
async def mark_updates_seen(event_id: str):
ctx = _ctx()
event = await ctx.store.get_event(event_id)
if event is None:
return JSONResponse(status_code=404, content={"error": "not found"})
for update in event.updates:
update["seen"] = True
await ctx.store.save_event(event)
return {"marked_seen": len(event.updates)}
Comment thread loom/state/store.py
"""Upsert an event (insert or update by ID)."""
async with self._session() as session:
existing = await session.get(EventRow, event.id)
row = _event_to_row(event, existing)
Comment thread loom/api_server.py
Comment on lines +341 to +370
@app.post("/api/events/{event_id}/analyze")
async def analyze_event(event_id: str):
"""Trigger agent re-analysis on a tracked event (manual)."""
ctx = _ctx()
event = await ctx.store.get_event(event_id)
if event is None:
return JSONResponse(status_code=404, content={"error": "not found"})
if event.status != "active":
return JSONResponse(status_code=400, content={"error": "event is not active"})

# Build context from event + updates
update_parts = []
for u in event.updates:
update_parts.append(
f"[{u.get('created_at', '')}] {u.get('author', 'unknown')}:\n{u.get('body', '')}"
)
context = f"## Item: {event.title}\n\nSource: {event.source_id}\n\n"
if event.metadata.get("html_url"):
context += f"URL: {event.metadata['html_url']}\n\n"
if update_parts:
context += f"## Recent Updates ({len(update_parts)} new)\n\n"
context += "\n---\n".join(update_parts)

# Use a fire-and-forget query_once for the analysis
try:
session = await ctx.session_mgr.query_once(
prompt=f"Analyze the following tracked GitHub item and its updates. \
Provide a concise summary of what's happening and whether action is needed.\n{context}",
envelope_id=event.envelope_id,
)
Comment thread loom/api_server.py
Comment on lines +375 to +377
except Exception as exc:
logger.error("Error analyzing event %s: %s", event_id, exc)
return JSONResponse(status_code=500, content={"error": str(exc)})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants