Secretary Agent
An always-on Telegram bot that turns voice-of-developer messages into GitHub tickets and keeps the project board current without manual upkeep.
Overview
┌─────────────────────────────────────────────────────────────────┐
│ You (Developer) │
│ "add earnings heatmap" │
└──────────────────────────────┬──────────────────────────────────┘
│ Telegram message
▼
┌─────────────────────────────────────────────────────────────────┐
│ secretary/bot.py │
│ (long-poll loop, always-on) │
└──────┬────────────────────┬────────────────────────────┬────────┘
│ │ │
▼ ▼ ▼
┌──────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ DeepSeek V3 │ │ GitHub Projects │ │ Telegram reply │
│ intent.py │ │ GraphQL API │ │ "✅ Ticket #42 │
│ classify + │ │ github_projects │ │ added to Backlog"│
│ generate │ │ .py │ └──────────────────┘
└──────────────┘ └────────┬─────────┘
│
▼
┌───────────────────────┐
│ GitHub Project Board │
│ marketpulse (#1) │
│ │
│ Backlog → In Progress│
│ → In Review │
│ → Done │
└───────────┬───────────┘
│ reads board
▼
┌───────────────────────┐
│ Night Agent │
│ (Claude Code session)│
│ moves tickets as │
│ it works │
└───────────────────────┘
| What |
Why it matters |
| Telegram is the input |
No app to open — just send a message from anywhere |
| DeepSeek V3 classifies intent |
Paid per-call (~$0.0004/message), fast, handles natural language |
| GitHub Projects is the source of truth |
Board is always live; night agent reads it directly |
| Night agent moves tickets automatically |
Board stays current with zero manual upkeep |
How It Works
Step 1 — You send a Telegram message
You ──► "add a bull/bear score to the portfolio page"
or
"what's in progress?"
or
"bug: TSLA sentiment not updating"
- Input: free-form text in your authorized Telegram chat
- Output: message received by the polling loop
- Who does it: Telegram (the messaging app),
TELEGRAM_CHAT_ID gates access
Step 2 — Intent classification
Raw text
│
▼
┌─────────────────────────────────────────────────┐
│ DeepSeek V3 (deepseek-chat, api.deepseek.com) │
│ │
│ System prompt: 5-intent classifier │
│ One API call, no conversation history │
│ │
│ Returns structured JSON: │
│ { │
│ "intent": "new_feature", │
│ "title": "Bull/Bear score on portfolio", │
│ "body": "Add a composite score ...", │
│ "priority": "medium" │
│ } │
└─────────────────────────────────────────────────┘
- Input: raw Telegram message text
- Output: structured JSON with intent + generated ticket content
- Who does it:
secretary/intent.py
Step 3 — Duplicate check (new_feature / new_bug only)
Before calling DeepSeek to generate a ticket:
github_projects.py → fetch all open ticket titles + statuses
│
▼
Pass to DeepSeek as context alongside the developer's message
│
├── near-duplicate found?
│ → intent = "duplicate"
│ → reply: "ticket already exists: '<title>' (In Progress)"
│ → stop, no ticket created
│
└── no duplicate → proceed to ticket generation
Step 4 — Dispatch on intent
intent = "new_feature" ──► create GitHub issue → add to board Backlog
intent = "new_bug" ──► create GitHub issue (bug label) → Backlog
intent = "duplicate" ──► Telegram reply naming the existing ticket
intent = "status" ──► query board → summarize counts + last 5 Done
intent = "move_ticket" ──► find ticket by name → move to requested status
intent = "question" ──► answer from git log / board state, no ticket
- Input: classified intent + params
- Output: action taken (ticket created, board updated, or answer drafted)
- Who does it:
secretary/bot.py dispatcher → secretary/github_projects.py
Step 5 — GitHub Projects write (create or move)
For new tickets — two-step sequence:
gh issue create ← creates a real GitHub issue
│
▼ issue number
gh project item-add ← attaches issue to the board
│
▼ item_id
updateProjectV2ItemFieldValue ← sets Status = "Backlog"
(GraphQL mutation)
- Input: ticket title, body, priority from DeepSeek
- Output: ticket visible on the GitHub Projects board at Backlog
- Who does it:
secretary/github_projects.py
Step 6 — Telegram confirmation
Secretary ──► "✅ Ticket created: Bull/Bear score on portfolio
Status: Backlog | Priority: medium
https://github.com/…/issues/42"
- Input: result from dispatch
- Output: confirmation (or error) sent back to your Telegram chat
- Who does it:
secretary/bot.py → requests.post to Telegram Bot API
Step 7 — Night agent picks up and moves tickets
Night agent wakes (scheduled or manual)
│
├── list_tickets(status="Backlog") ← find next item
├── move_ticket(item_id, "In Progress")
│
│ [does the work — implements, tests, commits]
│
├── move_ticket(item_id, "In Review") (optional QA gate)
└── move_ticket(item_id, "Done")
- Input: GitHub Projects board (Backlog column)
- Output: ticket progresses through the board automatically
- Who does it: night agent (Claude Code session) using
secretary/github_projects.py
Components
secretary/
├── bot.py ─── entry point, Telegram polling loop
├── intent.py ─── DeepSeek call, returns structured JSON
└── github_projects.py ─── shared GitHub Projects GraphQL wrapper
│
└── used by both bot.py AND the night agent
| Component |
What it does |
Lives in |
bot.py |
Long-polls Telegram every 10s, routes messages to intent → dispatch |
secretary/bot.py |
intent.py |
One-shot DeepSeek V3 call — classify + generate ticket content |
secretary/intent.py |
github_projects.py |
GraphQL wrapper for the marketpulse Projects board |
secretary/github_projects.py |
| Config constants |
GH_PROJECT_ID, GH_STATUS_OPTION_IDS, SECRETARY_MODEL, etc. |
config.py |
Data Flow
Developer (Telegram)
│
│ "fix: VIX not showing on macro tab"
▼
bot.py (10s poll loop)
│
│ raw text
▼
intent.py
│ POST https://api.deepseek.com/v1/chat/completions
│ model: deepseek-chat
▼
DeepSeek V3
│ { intent: "new_bug", title: "VIX missing...", body: "..." }
▼
bot.py dispatcher
│
│ create_ticket("VIX missing...", label=bug)
▼
github_projects.py
│ gh issue create → gh project item-add → GraphQL set Status=Backlog
▼
GitHub Projects Board
│ Backlog: [VIX missing on macro tab]
▼
Night agent (next run)
│ move → In Progress → [works] → Done
▼
GitHub Projects Board
Done: [VIX missing on macro tab ✅]
│
│ (optional) secretary syncs BACKLOG.md mirror
▼
Developer (Telegram)
"✅ Bug ticket created: VIX missing on macro tab"
Intent Reference
| Intent |
Trigger phrases |
Action |
new_feature |
"add", "build", "I want", "feature:" |
Duplicate check → GitHub issue → Backlog |
new_bug |
"bug:", "broken", "not working", "fix:" |
Duplicate check → GitHub issue (bug label) → Backlog |
duplicate |
(detected by DeepSeek against open ticket list) |
Telegram reply naming existing ticket + status |
status |
"what's in progress", "board status", "what are you working on" |
Query board → summary reply |
move_ticket |
"move X to done", "mark X complete" |
updateProjectV2ItemFieldValue |
question |
"what does X do", "why is Y" |
Answer from git log / board, no ticket |
Key Decisions
| Decision |
Chosen approach |
Why |
| Model choice |
DeepSeek V3 via direct API |
~$0.27/MTok input, $1.10/MTok output — each ticket involves intent classification + context gathering + full ticket generation; expect ~5k–20k tokens in / 1k–3k out (~$0.005–$0.02/message); OpenAI SDK compatible |
| Ticket creation |
Real GitHub issues (not draft items) |
Night agent can link commits; history survives project deletion |
| Board state |
GitHub Projects as source of truth |
Live, visual, shared; BACKLOG.md becomes a mirror only |
| Authorization |
TELEGRAM_CHAT_ID allowlist |
Simple, no OAuth needed; you're the only user |
| Polling vs webhook |
Long-polling (getUpdates) |
No public URL needed; runs behind any NAT |
| Shared helper |
github_projects.py used by both bot and night agent |
One place to update when GraphQL field IDs change |
What Can Go Wrong
| Failure |
Impact |
How we handle it |
| DeepSeek API down |
Intent classification fails |
Bot replies with error; user can retry |
| Telegram token invalid |
Bot receives no messages |
Process exits with clear error on startup |
GH_TOKEN missing / wrong scope |
Ticket creation fails |
github_projects.py raises with descriptive error; bot replies |
| Status field IDs stale |
move_ticket silently no-ops |
Fetch fresh IDs via GraphQL and update config.py |
| Night agent crashes mid-ticket |
Ticket stuck "In Progress" |
Manually move via move_ticket Telegram command |
| Unauthorized chat sends message |
Security risk |
Bot silently ignores any chat_id ≠ TELEGRAM_CHAT_ID |
Running
# Start the secretary bot (keep alive — use tmux or systemd)
python secretary/bot.py
# Or via Make
make secretary # starts bot.py in background
# The secretary runs SEPARATELY from main.py (not part of the scheduler)
Glossary
| Term |
Plain English definition |
| Secretary agent |
The bot you text on Telegram; it manages your to-do board for you |
| Intent |
What the bot thinks you mean — "new feature request" vs "check status" vs "report a bug" |
| DeepSeek V3 |
A cheap, fast AI model (like a lightweight ChatGPT) used to understand your message and write the ticket |
| GitHub Projects |
GitHub's built-in project board — like Trello but lives next to your code |
| Item ID |
GitHub's internal ID for a card on the board — used to move it between columns |
| Status field |
The column a ticket sits in: Backlog, In Progress, In Review, Done |
| Long-polling |
The bot repeatedly asks Telegram "any new messages?" every 10 seconds — no server or webhook needed |
| GraphQL |
The query language GitHub uses for its Projects API — the bot sends structured queries to read and update the board |
GH_TOKEN |
A GitHub access token with project scope — gives the bot permission to create and move tickets |
| Night agent |
A separate AI coding session that runs at night, picks up Backlog tickets, and implements them |
| BACKLOG.md |
A human-readable text file that mirrors the board — kept for git history but no longer the source of truth |
| Dispatch |
The step where the bot decides which action to take after it knows the intent |
Last updated: 2026-06-02 · Owner: Michael Ko