Skip to content

Proposal: Tickets/Goals/Heartbeats unification with shared Telegram approval gate #123

Description

@sistemabritto

Summary

Proposing Tickets/Goals/Heartbeats unification with a shared Telegram approval gate — implemented and running in production on a fork, but built on top of custom infrastructure that doesn't exist in develop yet (details below). Opening this as a discussion/proposal before investing in a real port, since the underlying dependency gap is bigger than the feature itself.

Problem

Today (in the fork, and likely in develop too — happy to be corrected) there's no single mechanism for an autonomous Goal to become tracked, assigned work with human oversight on the risky parts:

  • A human-authored Goal has no automatic path to becoming actionable tickets.
  • Ticket → Goal progress rollup had (in the fork's history) multiple independent writers to goals.current_value, causing drift/double-counting.
  • Nothing stops an autonomous agent from publishing to a public channel (social media, external comms) without a human in the loop, or from proposing new sub-goals recursively without bound.

What was built (fork, production-tested)

  • Single source of truth for goals.current_value — one function (_recompute_goal_from_tickets, COUNT-based over tickets.status IN ('resolved','closed')) replaces what were previously 3-4 independent writers (a SQLite trigger + manual sync + raw PATCH). Verified idempotent on reopen/re-resolve.
  • goal-planner heartbeat — wakes on a goal_created event (top-level Goals only, guarded by parent_goal_id IS NULL to prevent recursive decomposition), reads .claude/agents/*.md to pick each ticket's assignee via closed-set validation (invalid slug routes to a triage bucket instead of orphaning the ticket).
  • Self-healing review loop — a ticket marked review gets a structured pass/fail verdict before reaching resolved; bounded retries (2 bounces) before parking for a human, with the bounce counter reset scoped to the last manual reopen (not global).
  • Shared approval gate (pending_approvals table, one mechanism for two use cases):
    • Publish-gate: an agent capable of publishing externally never resolves a ticket without an explicit Telegram approve/reject. Fail-closed by default (only an explicit publish_intent: false bypasses).
    • Decomposition-gate: an agent proposing a sub-goal (not just tickets) requires a second approval before any ticket is created from that sub-goal. Approving creates the tickets directly from the already-approved payload — deliberately not by re-firing the goal_created event, since that would bypass the recursion guard (approval-triggered re-wake is a separate code path from goal creation, and only the latter was originally guarded — this was caught in review before it shipped).
    • Dedicated bridge token for the approval-decision endpoint, isolated from the general admin API token and from the agent subprocess's own environment (an agent can't read its own approval-bridge credential and self-approve).
  • Never fabricates a "published" success — the actual external-publish action returns published=false honestly when no automated integration exists for a channel, rather than claiming success without confirmation. (This traces back to a real incident where an orchestrator once reported "posted successfully" without actually posting — the whole approval-gate design exists to make that class of bug structurally harder.)

Full ADR-style writeup (architecture decisions, threat model, alternatives considered) available if useful — happy to share.

Why this is a discussion, not a PR yet

The feature is built on top of two things this fork added independently that don't exist in develop:

  1. A multi-provider fallback/routing layer for the agentic subprocess calls (~900 lines) — develop's heartbeat runner invokes the claude binary directly, single-provider.
  2. An outcome-driven heartbeat notification system (~800 lines) replacing a generic "heartbeat ran" ping with structured, agent-reported outcomes (work/skip/blocked) that this feature's review-loop and approval-gate build directly on top of.

Porting just the tickets/goals/approval-gate feature without also reconciling those two pieces isn't realistic — they're load-bearing. Before investing effort in a real port (or a from-scratch reimplementation against develop's simpler heartbeat runner), I'd like maintainer input:

  • Is a multi-provider fallback layer something develop wants at all, or is single-provider (claude CLI) a deliberate simplicity choice?
  • Is the tickets/goals/heartbeats unification concept (independent of my specific implementation) something that fits the roadmap?
  • Any existing design docs/discussions on this that I should read before proposing an implementation?

Open to whatever shape of contribution makes sense here — a design doc, a smaller isolated piece first, or shelving it if it doesn't fit direction.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions