Skip to content

Plan: Secretary Agent

Goal

A cheap, always-on bot that bridges Telegram → GitHub Projects → night agent. You send a message describing a feature, bug, or question; the secretary creates a ticket on the GitHub Projects board and queues it for the night agent. As the night agent works, it moves tickets through Backlog → In Progress → Done so the board stays current without manual upkeep.

Model

DeepSeek V3 via api.deepseek.com (direct API, no OpenRouter markup).

Base URL https://api.deepseek.com/v1
Model deepseek-chat (V3)
Pricing ~$0.27/1M input · ~$1.10/1M output
Auth DEEPSEEK_API_KEY (already in config.py)
SDK openai Python SDK pointed at DeepSeek base URL

At typical secretary usage (a few messages/day + lightweight intent parsing), cost is effectively zero.

Architecture

You
 │  Telegram message
 ▼
secretary/bot.py  (polling loop, always-on)
 │
 ├── DeepSeek V3  ←  intent classification + ticket body generation
 │
 ├── GitHub Projects GraphQL API
 │     gh issue create + gh project item-add  → new Backlog ticket (never createProjectV2DraftIssue — creates drafts)
 │     updateProjectV2ItemFieldValue → move ticket status
 │     listProjectV2Items → status queries
 │
 └── Telegram reply  ←  confirmation / status summary

Night agent (reads GitHub Projects board, not BACKLOG.md)
 │  on start: move ticket In Progress
 │  on done:  move ticket Done
 └── secretary/github_projects.py  (shared helper)

Intents

The secretary classifies each Telegram message into one of:

Intent Action
new_feature Create Backlog ticket with title + description generated by DeepSeek
new_bug Create Backlog ticket tagged as bug
status Query board, reply with counts per column and last 5 Done items
move_ticket Move named ticket to a specified status
question Answer from project state (BACKLOG.md, recent git log) — no ticket created

Components

secretary/bot.py

Main entry point. Long-polls Telegram using raw requests (no extra SDK needed — we already call Telegram in notifications/telegram.py). Authorized by TELEGRAM_CHAT_ID — any message from a different chat is silently ignored.

while True:
    updates = telegram_get_updates(offset)
    for update in updates:
        if str(update.chat_id) != TELEGRAM_CHAT_ID:
            continue
        intent, params = classify(update.text)   # DeepSeek call
        result = dispatch(intent, params)
        telegram_send(TELEGRAM_CHAT_ID, result)
    sleep(SECRETARY_POLL_INTERVAL)               # default 10s

secretary/github_projects.py

Thin wrapper around the GitHub Projects v2 GraphQL API. Shared by both the secretary bot and the night agent so neither duplicates the GraphQL logic.

Key functions: - create_ticket(title, body, status="Backlog") -> item_id — creates a real GitHub issue then adds it to the project board (two-step: gh issue create + gh project item-add) - move_ticket(item_id, status) — status one of Backlog | In Progress | In Review | Done - list_tickets(status=None) -> list[dict] - find_ticket(title_fragment) -> item_id | None

Uses GH_TOKEN env var (same token gh CLI uses — gh auth token to retrieve).

secretary/intent.py

DeepSeek call with a short system prompt. Returns structured JSON:

{
  "intent": "new_feature",
  "title": "Add earnings surprise heatmap to Overview",
  "body": "Show EPS surprise % for all tickers as a colour-coded grid ...",
  "priority": "medium"
}

System prompt keeps it minimal — one round-trip, no conversation history needed.

Night agent changes

The night agent currently reads BACKLOG.md. With this plan, it additionally:

  1. At session start: calls list_tickets(status="Backlog") to pick the next feature
  2. Before starting work: calls move_ticket(item_id, "In Progress")
  3. After QA passes: calls move_ticket(item_id, "Done")

BACKLOG.md is kept as a human-readable mirror but is no longer the source of truth. The secretary syncs it after each board change (optional, for git history).

Config additions (config.py)

SECRETARY_POLL_INTERVAL = 10          # seconds between Telegram polls
SECRETARY_MODEL         = "deepseek-chat"
SECRETARY_BASE_URL      = "https://api.deepseek.com/v1"
GH_PROJECT_ID           = "PVT_kwHOBpDtN84BZY1n"   # marketpulse project
GH_PROJECT_STATUS_FIELD = "PVTSSF_..."               # field ID for Status column
GH_STATUS_OPTION_IDS    = {           # option IDs for each status value
    "Backlog":     "...",
    "In Progress": "...",
    "In Review":   "...",
    "Done":        "98236657",
}

Field and option IDs are fetched once via GraphQL and hardcoded — they don't change.

Running

# Secretary bot (keep running — systemd, tmux, or pm2)
python secretary/bot.py

# Or via make
make secretary          # adds a target that starts bot.py in background

The secretary is a separate process from main.py — it does not start with the scheduler.

Implementation Checklist

  • [ ] Add DEEPSEEK_API_KEY note to .env docs (already in config.py, just needs key)
  • [ ] Add GH_TOKEN to .env (used for Projects GraphQL writes)
  • [ ] Add SECRETARY_* and GH_PROJECT_* constants to config.py
  • [ ] Fetch and record Status field ID + option IDs via GraphQL
  • [ ] Write secretary/github_projects.py
  • [ ] Write secretary/intent.py (DeepSeek classification)
  • [ ] Write secretary/bot.py (Telegram polling loop)
  • [ ] Update night agent (scheduler/jobs.py or night-flow skill) to call github_projects on start/done
  • [ ] Add make secretary target to Makefile
  • [ ] Test end-to-end: send Telegram message → ticket appears on board → night agent moves it

Open Questions

  • GH_TOKEN permissions: needs project scope. gh auth token on this machine may already have it — verify with gh auth status.
  • Status field IDs: must be fetched via projectV2 GraphQL query before wiring up config.
  • BACKLOG.md sync: keep it as a mirror (secretary writes it after each board change) or deprecate it? Suggestion: keep for git history, but treat board as authoritative.
  • Night agent trigger: does the secretary actively wake the night agent (e.g. via a signal file or API call), or does the agent just run on its fixed schedule and pick up whatever is in Backlog? Simpler to let the agent run on schedule — secretary just manages the board.