Subscription Lifecycle And Terminal Auto-Retire

Summary

AgentInbox should treat many task-scoped subscriptions as leased resources, not permanent configuration.

This RFC proposes:

The immediate motivation is PR-scoped or branch-scoped subscriptions that are not cleaned up after the task completes, leading to stale inbox traffic and growing background state.

Problem

Today Subscription already contains:

But the current runtime does not close the loop well enough:

In practice this creates:

Goals

Non-Goals

Core Design

1. Cleanup Policy Should Be Explicit

Task-scoped subscriptions should not rely on a special temporary category.

Instead, they should carry an explicit cleanup policy.

Suggested shape:

{
  "cleanupPolicy": {
    "mode": "on_terminal_or_at",
    "at": "2026-04-20T00:00:00Z",
    "gracePeriodSecs": 86400
  }
}

Suggested modes:

Examples:

This makes cleanup semantics explicit:

2. Tracked Resource Binding Must Be Explicit

Automatic retire should not depend on parsing filter.

The lifecycle binding should be stored explicitly on the subscription.

Suggested minimal shape:

{
  "trackedResourceRef": "pr:373"
}

This ref is scoped to the source instance.

Core matching would therefore use:

Why Not Global Provider Objects

The core does not need a universal object such as:

Those details belong to the source implementation.

The only thing core needs is a stable source-scoped opaque ref that identifies the tracked resource for that source.

3. Grace Period Is A Modifier On Terminal Cleanup

gracePeriodSecs is not a separate policy mode.

It only modifies terminal-based cleanup:

Its meaning is:

This is useful when a resource often emits trailing events after the main terminal transition.

4. Source Implementations Project Lifecycle Signals

Core should not know what “merged”, “closed”, “archived”, or “resolved” means for each provider.

Instead, source implementations should project generic lifecycle signals.

Suggested shape:

{
  "ref": "pr:373",
  "terminal": true,
  "state": "closed",
  "result": "merged",
  "occurredAt": "2026-04-13T10:00:00Z"
}

This signal may be produced by:

The core should only interpret:

5. Deadline Cleanup Remains The Fallback Safety Net

Lifecycle signals are useful but not sufficient on their own.

Reasons:

Therefore:

This makes cleanup robust instead of event-perfect.

Example: GitHub Pull Request

For a GitHub PR-scoped subscription:

Example:

{
  "trackedResourceRef": "pr:373",
  "cleanupPolicy": {
    "mode": "on_terminal_or_at",
    "at": "2026-04-20T00:00:00Z",
    "gracePeriodSecs": 86400
  }
}

The GitHub source implementation would project:

Core does not need to know GitHub semantics. It only consumes a terminal signal for sourceId + pr:373.

Subscription Shortcuts

Subscription shortcuts are a good place to make task-scoped subscriptions ergonomic.

But shortcut expansion must still compile to standard fields such as:

This keeps shortcut use compatible with:

Runtime Behavior

Registration

When a subscription with cleanup policy is added:

If trackedResourceRef is omitted:

Event Processing

When a source event is read:

  1. append the normalized inbox event as usual
  2. if the source implementation projects a lifecycle signal:
    • find subscriptions on the same sourceId
    • match trackedResourceRef
    • if terminal cleanup is enabled by cleanupPolicy.mode, schedule or perform retire

This should not interfere with ordinary inbox delivery.

GC

Background lifecycle GC should perform at least two cleanup passes:

  1. expired deadline-based subscriptions
  2. subscriptions already marked for delayed terminal cleanup whose grace window has elapsed

Removal Semantics

Auto-retire should remove or deactivate the subscription itself.

It should not remove the shared source.

This is important because:

CLI And API Direction

The lifecycle fields should be visible in normal subscription inspection.

Suggested add/update inputs:

CLI may later add convenience flags, but the canonical model should remain structured and transport-neutral.

Why Explicit trackedResourceRef

The alternative is to infer lifecycle binding from filter.

That is fragile because filters may:

Lifecycle binding is not the same thing as event matching.

So the system should store it explicitly.

Migration Direction

Suggested incremental rollout:

  1. add trackedResourceRef and cleanupPolicy to the subscription model
  2. honor deadline-based cleanup in background GC
  3. expose lifecycle capability in resolved source schema
  4. let source implementations project lifecycle signals
  5. add source-specific shortcut expansion that fills cleanup policy fields

This delivers value early without requiring a full end-to-end redesign first.

Open Questions