What phpbotscout does not do¶
Documentation usually describes what software does. This page describes what it will not do, which is the other half of the question people actually ask. Every entry here is either not built yet, deliberately unsupported, or a constraint of how the thing works — and each says which.
It posts only where a server opts in, and cannot moderate yet¶
A decision, and a gap. phpbotscout serve answers every question addressed to it in the
server's public channels. By default it posts none of them: the answer is composed and recorded,
and the channel stays quiet (shadow mode). A server posts only when its post_answers is set.
A server that does not post has its scope built read-only, so it has no actor at all: posting,
threads and reactions are absent from the type there, not switched off behind a check. See
discord.guilds[].post_answers.
| Asked about | Today |
|---|---|
| Answering a question in a channel | composed and recorded everywhere; posted where post_answers is set |
| Opening a thread and adding the 👍 / 👎 reactions | where post_answers is set |
| Recording those reactions as feedback | where post_answers is set, from v0.9.0 |
| Flagging content for moderators | not built |
| Raising a GitLab issue on someone's behalf | not built |
/ask or /appeal slash commands |
not built |
Forgetting a member disconnects them rather than deleting what they said¶
forget finds one member's questions, answers and votes by the salted hash of
their ID, lists them, and disconnects the member from them. It deletes nothing: the question, the
answer and the vote stay, because they were said in public and every count is built on them. What
it removes is the hash and the Discord IDs that lead to a thread naming the member
(spec 0032).
It cannot reach the host's logs or backups. Those keep 30 days only if the host is set up to, as handling a request to be forgotten describes.
There is still no moderation record and no message archive, so a request phrased as "delete this user's moderation record" has no operation behind it, not because the request is refused but because nothing has written one.
The bot also does not learn anything about the person asking. ask is one-shot by construction: no
conversation is retained between questions, and one person's question is never part of the context
for the next person's.
It serves one project's servers, not several organisations¶
Deliberate. discord.guilds takes several servers, such as a stage server and a production
server, served by one application with one token. Their data is kept apart in one database, and the
documentation index is shared
(spec 0029).
Multi-tenancy is still a non-goal. A second organisation's server, with its own credentials, corpus
and policy, is not supported. The same space configured twice is a startup error:
It reads only public channels, and receives more than it keeps¶
A decision. The bot reads every text channel and forum that everyone in the server can view,
and the public threads under them, except channels named in exclude_channels. A channel only some
roles can see is never read, even if the bot is addressed there. When the bot cannot establish a
channel's visibility, it drops the message. See
which channels the bot reads and
spec 0024.
A constraint of Discord, not a choice. Discord's message intents are guild-wide. There is no way to subscribe to only the public channels, so the gateway delivers every message in the guild and the bot discards the ones it must not read.
Nothing about a message it does not read is retained: not its content, not its channel, not its author, only one unlabelled counter. See what it reads, and what it keeps.
Direct messages, private channels, private threads, announcement channels and voice (including the text chat of voice channels) are not read at all.
It cannot index a repository you name¶
Deliberate, and a security boundary. There is no configuration key, flag or file that adds a source. The corpus is enumerated from the forge and filtered by a predicate every run.
A configuration file asserting that a repository is public would replace a live check with an operator's memory — and a repository made private yesterday looks identical to one that is still public. See what the bot is allowed to read.
The same applies in reverse: there is no command that removes one source from the index. When a repository stops qualifying, or stops appearing in the group listing because it was renamed, moved or deleted, the next refresh retires it and its pages stop being cited.
It cannot use OpenAI, Gemini, or a local model server¶
A constraint today, and a hard one. Only the Anthropic provider module is linked into the
binary, so only claude and claude-local are usable. Any other name is refused when the client
is built:
It is not something configuration can change. A provider has to be linked into the build, and it
would also have to support the one-shot client ask builds; see ai for why
one-shot is load-bearing.
openai-compatible, which is how you would reach Ollama or a self-hosted endpoint, fails on the
base URL first, because phpbotscout has no configuration key for one:
ERRO ask: build openai-compatible client: unable to construct client: invalid chat provider base URL hints="ProviderOpenAICompatible requires Config.BaseURL to be set"
Supplying one would not help, because no OpenAI provider is linked in.
There is also no way to use one provider for one task and another for a second: ask builds a
single client, and there is no second task yet to give a different one to.
Retrieval is keyword search, not semantic search¶
A constraint of the current implementation. Ranking is BM25 over SQLite FTS5. There is no embedding model, no vector index and no re-ranking step. A question phrased entirely in words the documentation does not use will not find the page that answers it, however close the meaning.
Three things soften that, and none removes it. Terms are stemmed by the porter tokenizer. Code
chunks carry the split form of their identifiers, so defaultTakeCount is reachable as "default
take count". And a question that names a module, as go/config, "the Go config module" or keryx,
or is asked in a project's support forum, fills five of its eight passages from that module, and
never from its twin in the other language (spec 0030).
A module name that is also an ordinary word, such as errors or config, counts only when it is
qualified: go/errors, "Go errors", "the errors module", or asked in a forum holding exactly one
module of that name. Whether a name is ordinary is measured from the index, not listed: it is
distinctive when it is hyphenated, or when at least 40% of the passages mentioning it are its own
module's.
There is no configuration key for a relevance threshold. Calibration found that no score separates
a question the corpus answers from one whose subject it merely covers — the bands overlap, and a
floor set high enough to exclude the second takes 40% of the first with it. --limit is the only
retrieval control.
An answer can only be as current as the last successful refresh¶
A constraint of how indexing works. The index is a snapshot. serve re-reads the corpus every
index.refresh_interval — an hour by default — and a source that fails stays at whatever commit it
was last indexed at.
A stale index deliberately does not fail readiness. Answering from what is already indexed,
with a caveat, beats not answering, and taking the bot out of service would not fix a forge nobody
can reach. index status is where staleness surfaces.
It will not answer from issue threads¶
Deliberate. An answer that exists only in an issue comment is a documentation gap, and the bot is built to surface gaps rather than paper over them. Issue text is never answer content; an issue may only point at documentation or code, and that target must satisfy the corpus rules on its own.
The cost is real: a maintainer's explanation in an old thread cannot be used. See what the bot is allowed to read.
It points at an upstream's documentation rather than answering for it¶
Deliberate. Some projects wrap another: ffmpeg-wasi builds FFmpeg, comms-discord is built on disgo, config-vault on HashiCorp Vault's client. A question that belongs to the upstream, such as which audio bitrates an FFmpeg encoder supports, is not answered from the upstream's documentation, because that documentation describes everything the upstream can do, not the subset a wrapper builds, and an upstream detail stated wrongly would read as this estate's claim.
Instead, when the bot declines such a question or answers it only in part, it adds one line naming
the upstream, the version the wrapper's release builds where it is known, and where the upstream's
documentation is. The line is a fixed sentence built from configuration, never written by the
model, and the decline is still recorded as a gap. Which upstreams it knows is
answer.upstreams.
--output json does not change command output¶
A gap, not a decision. The global --output flag is accepted by every command, but ask,
index status and index sources write formatted text regardless. There is currently no
machine-readable output from any of them.
Releases are not signed¶
A gap, not a decision. Each release publishes a linux/amd64 archive and checksums.txt, and
nothing else. The checksum shows the archive arrived intact; nothing shows who built it. See
Getting started.