Roadmap — where the work has got to¶
Updated 2026-07-29.
This page carries status only. What each phase is, what it depends on and what "done" means all live in the delivery plan, which is a dated design record and deliberately does not track progress — a spec that changes every week stops being a record of what was decided.
One fact, one home: if a phase's meaning is wrong, fix the plan; if its status is wrong, fix this page.
Key¶
| ✅ | done |
| 🔨 | in flight |
| ◐ | partially done, usable |
| ⏳ | not started |
| 🚧 | blocked — the blocker is named |
Track 0 — groundwork¶
| # | Task | Status | Notes |
|---|---|---|---|
| 0.1 | Register the Discord application | ✅ | Intents enabled, installed to the test guild |
| 0.2 | Dedicated test guild | ✅ | |
| 0.3 | Benchmark corpus | ◐ | 59 questions in two sections — calibration (38) and questions (21). Both harnesses load only calibration, so 21 questions are never exercised. All cites: keys are empty; populating them is 2c.2. Entries now carry an expect axis naming the outcome the bot should reach, because only one of the four decline shapes should raise a ticket |
| 0.4 | Adversarial prompt-injection corpus | ✅ | 24 entries in test/corpus/adversarial.yaml |
| 0.5 | Moderation test corpus | ⏳ | Blocks Phase 5 only |
| 0.6 | Public policy | ✅ | Provider named (Anthropic, Claude Haiku 4.5), what is and is not sent stated exactly, moderation marked not-yet-active |
| 0.7 | Enumerate corpus sources | ✅ | See corpus validation |
Delivery phases¶
| Phase | Status | Notes |
|---|---|---|
| 1a-0 — Discord library spike | ✅ | disgo selected |
1a — chat-platform + -discord |
✅ | Upstream, released |
| 1b — Discord ingest | ✅ | Gateway reads allowlisted channels, survives forced disconnect with session resume |
2 — knowledge index + ask |
✅ | Exit criteria met against the live group 2026-07-29. Rebuilt on v0.1.0 (2026-08-06): 53 of 103 sources qualify, 2,025 documents, 15,175 chunks — after an estate-wide documentation sweep added ~4,300 chunks and the internal-package exclusion removed 1,127 |
| 2b — provider benchmark | ◐ | Haiku 4.5 selected 2026-07-31 — decision record. £5/month at 50 questions a day. Provisional: citation accuracy (rubric #1) is unmeasured until 2c.2, and injection resistance is unscored |
| 2c — retrieval spikes | ◐ | 2c.1's original result is withdrawn. "Limit 50 gained 2 code answers" came from a harness missing Config.Stateless, so all 72 calls shared one conversation and a question could be answered from an EARLIER question's passages (fixed in !25). Re-run clean on the v0.1.0 index: limit 8 → 50 converts nothing. The docs band is saturated at 20/20 at limit 8, so answered/declined can no longer discriminate at all. Three-model sub-question arbitration found the real signal — four questions answer INCOMPLETELY with the missing passages at ranks 10-24, which a boolean cannot see. Raising answer.DefaultLimit is blocked on #3 |
| 3 — Discord answering | 🚧 | Needs 2b and 0.6 |
4a — forge issue capability |
✅ | Delivered upstream 2026-08. forge v0.9.2 ships Issues (search, get, comments) and IssueFiler (create) as separate optional capabilities — split because read needs a read-only scope and write does not. forge-gitlab v0.6.4 implements both. Exit criteria verified here: a provider lacking them fails forge.As cleanly rather than panicking. Ships more than was asked for — Sanitise by default (R-ESC-9), an idempotency key for at-most-once filing, and search semantics where a FAILED duplicate search is never the same answer as "no duplicates" (R-ESC-3) |
| 4b — GitLab escalation | 🚧 | Unblocked — 4a is released. Should follow #3: escalation turns a decline into a filed issue, and a composer that misfiles a refutation as a decline would file tickets for questions it can already answer |
| 4c — gap explorer | ⏳ | |
| 5 — moderation | 🚧 | Needs 0.5 |
| 6 — feedback loop | 🚧 | Needs 3 |
What is actually next¶
2c.2 needs a human, and deliberately so. Expected citations cannot be derived from what the benchmark models cited: recall@k asks whether the right passage is in the top k, and ground truth taken from what a model cited when handed the top 8 is inside the top 8 by construction — recall would read ~100% however bad retrieval was, grading the retriever against its own output.
test/corpus/citation-shortlist.draft.yaml makes the authoring tractable: 25
ranked candidates per answerable question, to keep or replace. A question whose
answer is not in the pool is the most valuable row in the file, being a recall
failure the current metric cannot see.
2c.1's result is withdrawn, and the metric it used is exhausted. The original
finding — deeper retrieval gaining two code answers — came from a harness that
never set Config.Stateless, so every call carried every call before it and a
question could be answered from an earlier question's passages. Re-run clean on
the v0.1.0 index, limit 8 → 50 converts nothing, and the docs band answers 20 of
20 at limit 8. A boolean that is saturated cannot measure anything.
Sub-question decomposition can. Three independent models (gpt-5.5, gpt-5.6-sol, gpt-5.6-luna), each judging every question separately, agreed on 31 of 36 and found four questions answered INCOMPLETELY — with the missing passages at ranks 10, 14, 21 and 24. Limit 25 would close all four; limit 8 closes none. That is the reranker signal, and it was invisible to answered/declined.
Raising answer.DefaultLimit is nonetheless blocked on
#3: at limit 50 the
composer declined a question whose premise the passages refute, stating the
correct answer inside the decline reason. Trading a completeness gain for a
correctness loss is not an improvement, and the current metric would show
neither.
4a is delivered. forge v0.9.2 and forge-gitlab v0.6.4 ship the issue
capabilities, split into Issues and IssueFiler so a read-only deployment can
decline the write path structurally rather than discovering the limit on its
first attempt to file. Nothing is needed here to consume them.
4b should follow #3, not precede it. Escalation turns a decline into a filed GitLab issue. A composer that misfiles a refutation as a decline would file tickets asking for documentation that already exists and was already retrieved — the exact noise R-ESC-5's provenance labelling exists to keep out of the tracker.
Approved but unscheduled¶
| Spec | Why it is waiting |
|---|---|
| Published-site ingestion | Approved 2026-07-29. Delivers the tutorial tier, which no source currently covers — the blog repository is private and stays that way. Sequencing against 2b and 2c is undecided (its Q-4) |
Carried risks and chores¶
These are not phases and will not surface anywhere else.
| Item | Status |
|---|---|
Rotate DISCORD_PHPBOTSCOUT_TOKEN — exposed in a debug log during 1a |
⏳ |
Rotate GITLAB_TOKEN — surfaced by a test assertion during Phase 2 |
⏳ |
| Least-privilege deployment token (R-OPS-29) — development uses a full-access PAT | ⏳ Deployment phase |
| chat#4 — prompt caching | ✅ Closed. chat v0.9.1 ships CachingChatClient + Config.CacheTTL. Not adopted: our stable prefix is ~513 tokens, below every provider's cacheable minimum, so it would cost a cache-write premium and return nothing. The lever is moderation, where one rubric is sent per message |
| chat-anthropic#1 — map-form schema silently mangled | ✅ Closed. chat.Config.ResponseSchema is now typed *jsonschema.Schema, so the wrong shape is a compile error rather than silent mangling |
Report the gtb generate defects upstream — three command-scaffold non-compliances, plus the docs generator leaking its preamble above the frontmatter and stamping AI author bylines |
⏳ |
Release MR — cuts v0.1.0 when merged |
⏳ |