Seven services call a model directly, and the rule against it is enforced one layer too high
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 434
- Forks
- 21
- Avg merge
- 18m
- Merged PRs (30d)
- 326
Description
The rule and the hole
CLAUDE.md:
An agent may import a service; a service may never import an agent. … a service answers a question about state, an agent decides which question to ask, and a service calling an agent is asking the model what its own answer should be. Enforced by
TestNoNewServiceCallsAnAgent, which asserts zero.
The test asserts zero and passes. Meanwhile seven services reach a model directly through internal/ai:
| service | what it asks a model for |
|---|---|
chat |
composes replies in a room — carries its own prompts.json |
news |
summaries and sentiment |
blog |
— |
apps |
— |
images |
image generation |
text |
— |
web |
— |
The rule is enforced against an import path (agent/), so a service that imports internal/ai instead does the forbidden thing and passes. internal/ai is legitimately substrate — a service may know the runtime — which is exactly what makes it a hole rather than an obvious violation.
This is the shape agent/moderate was extracted to fix: "an analyzer function variable inside internal/flag, filled in at boot by service/chat: three services asking a model what their own answer should be, through an edge no layering test could see." The extraction fixed the three; the pattern stayed.
They are not all the same act
Worth settling before any sweep, or this "fixes" things into something worse:
- Generating an image is the state
imagesanswers about. A caller asks for an image and gets one; the model is the implementation, like an HTTP call to a provider. Same fortext. These are probably fine. - Composing a reply in a room is a judgement. So is deciding a headline is worth surfacing, which is why
agent/socialexists andservice/socialdoes not do it.
The line is roughly: is the model producing the answer the caller asked for, or deciding what the answer should be? The first is a service using a tool. The second is a service being an agent.
What to do
- Draw the line above properly and write it into CLAUDE.md next to the existing rule
- Move the judgement cases out —
chatfirst, tracked in #89, where it also unblocks XMPP - Make the test check what the rule means: a service reaching a model for a judgement, not merely the string
agent/in an import. Counted and pinned liketest/service_hooks_test.godoes for hooks, so the number cannot go up quietly - Leave the tool-shaped cases alone, with the reason recorded so they are not "fixed" later
Related
- #89 — chat specifically, and why it blocks the XMPP work
- The hooks ledger (
test/service_hooks_test.go) is the precedent for counting an edge rather than forbidding it: "a test that failed on the whole list would be deleted within a week."
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with CLAUDE.md, the seven services listed in the issue, and the existing TestNoNewServiceCallsAnAgent test. Compare its import-path check with the edge-counting precedent in test/service_hooks_test.go, and review #89 for the chat case. Done means the rule is defined, judgment cases are tracked or moved, tool-shaped cases are documented, and the test prevents the counted edge from growing.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go
- Domain
- ai, backend-api-design, testing-qa
- Issue type
- Refactor
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100