Skip to content
Merged

Dev #220

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
90 commits
Select commit Hold shift + click to select a range
dfc2270
Add multi-project scoping to onboarding generators (#167)
DaniloTatti Aug 29, 2026
06f7578
Integration of new blueprints into ai-service
BrainSkript Sep 9, 2026
92c9a28
Finished integration - Added parallel phase generation to speed up on…
BrainSkript Sep 11, 2026
0d39568
Merge branch 'dev' into feature/blueprint-integration
DaniloTatti Sep 13, 2026
5c9f579
Tell the mentor it is walking a path, and how far it may go
DavidLeuter Sep 13, 2026
d74a358
Say which half of the product answers which question
DavidLeuter Sep 13, 2026
9c9c0ab
Stop promising buttons that no tool call made
DavidLeuter Sep 13, 2026
2ee0d51
Tell the mentor a skip is the PM's call, and pin the no-tools notice
DavidLeuter Sep 14, 2026
887fa40
Raise steps that are ready to close, and offer refreshers after a miss
DavidLeuter Sep 14, 2026
3f1dbec
Teach the mentor to file skip requests with the hire's reason
DavidLeuter Sep 14, 2026
000519e
Tell the mentor a phase is a graph, and how to answer a ready step
DavidLeuter Sep 14, 2026
4d80f1c
Tell the mentor to pass both halves of a step's placement
DavidLeuter Sep 14, 2026
15bc8bb
Merge pull request #176 from SprintStartProject/enhancment/167-projec…
DaniloTatti Sep 14, 2026
0b23993
Use datastore revision for BM25 cache invalidation
Afif-del Sep 14, 2026
ab6f21a
Fix regression test formatting
Afif-del Sep 14, 2026
a3b4992
Bound BM25 rebuilds during corpus churn
Afif-del Sep 14, 2026
d9cc17f
Add durable artifact revocation tombstones
Afif-del Sep 14, 2026
6cb0b90
Test tombstone cleanup after ambiguous delete failure
Afif-del Sep 14, 2026
7363e26
Format revocation tombstone changes
Afif-del Sep 14, 2026
2ba1831
Preserve Chroma query result typing
Afif-del Sep 14, 2026
5751021
Keep revocation tombstone ownership isolated
Afif-del Sep 15, 2026
318c51b
Make the onboarding path the buddy's only onboarding
DavidLeuter Sep 15, 2026
7058853
Merge pull request #195 from SprintStartProject/fix/150-datastore-cor…
Afif-del Sep 16, 2026
e00ef19
Add membership tombstone recovery coverage
Afif-del Sep 16, 2026
fa6af1c
Add membership tombstone recovery coverage
Afif-del Sep 16, 2026
ca32e34
Add membership tombstone recovery coverage
Afif-del Sep 16, 2026
120c7fb
Recover stranded membership tombstones
Afif-del Sep 16, 2026
4b66e07
Recover membership tombstones across operation scopes
Afif-del Sep 16, 2026
89da970
Cover cross-scope membership tombstone recovery
Afif-del Sep 16, 2026
b6bc44c
Fix revocation recovery edge cases
Afif-del Sep 17, 2026
f0aadc9
Protect active membership revocations
Afif-del Sep 17, 2026
5b8805b
Merge pull request #192 from SprintStartProject/feature/blueprint-int…
BrainSkript Sep 18, 2026
5b307c5
Tell the buddy persona which mode it is in
LinseCed Sep 18, 2026
3c6b86d
Greet a manager in team mode instead of a new hire
LinseCed Sep 18, 2026
b0148b0
Merge pull request #196 from SprintStartProject/fix/151-durable-revoc…
Afif-del Sep 19, 2026
5151c1e
Widen retrieval with safe neighboring context
Afif-del Sep 19, 2026
2b29d1a
Honor artifact revocations in context reads
Afif-del Sep 19, 2026
cdcae76
Support industry-aware retrieval and prompts in phase assembly (#179)
DaniloTatti Sep 19, 2026
b5b843f
Merge pull request #197 from SprintStartProject/fix/163-retrieval-con…
Afif-del Sep 19, 2026
0858d3f
Include industry in phase assembly corpus fingerprint
DaniloTatti Sep 20, 2026
dfbbacc
Merge pull request #201 from SprintStartProject/feature/179-phase-ass…
DaniloTatti Sep 20, 2026
0a93fc0
Support optional project context in skill suggestion (#299)
DaniloTatti Sep 20, 2026
062520b
Merge pull request #202 from SprintStartProject/feature/299-skill-sug…
DaniloTatti Sep 20, 2026
dfcf58e
Merge remote-tracking branch 'origin/dev' into feature/311-buddy-onbo…
DavidLeuter Sep 21, 2026
cc0cb80
Stop the mentor gating real work behind the path
DavidLeuter Sep 21, 2026
1aa8e69
Merge pull request #199 from SprintStartProject/feature/194-team-gree…
LinseCed Sep 21, 2026
57883f0
Merge pull request #198 from SprintStartProject/feature/193-buddy-mod…
LinseCed Sep 21, 2026
691cd60
Route a request to pick work through the suggestion tool
LinseCed Sep 21, 2026
8231e69
Merge pull request #203 from SprintStartProject/feature/200-route-sta…
LinseCed Sep 21, 2026
7522f50
Ignore per-developer Hermes worktree files
daniilperkin Sep 24, 2026
dfb3a89
Report artifact index status to the knowledge base
daniilperkin Sep 24, 2026
19e2a95
Merge origin/dev into feature/311-buddy-onboarding-tutor
DavidLeuter Sep 24, 2026
65f6f88
Expect the tutor identity in dev's buddy mode tests
DavidLeuter Sep 24, 2026
1660bc5
Tell the mentor phases are a choice, not a queue
DavidLeuter Sep 24, 2026
945cb5e
Correct broken orientation JSON once instead of dropping the packet
DavidLeuter Sep 24, 2026
b826174
Correct broken JSON once when grading answers and drawing diagrams
DavidLeuter Sep 24, 2026
bf99a17
Tidy the JSON retries and point the mentor at a hire's last wrong answer
DavidLeuter Sep 24, 2026
7c78404
Merge pull request #204 from SprintStartProject/feature/KB-updates-final
daniilperkin Sep 25, 2026
f2421d7
Merge origin/dev into feature/311-buddy-onboarding-tutor
DavidLeuter Sep 25, 2026
ed6de94
Keep Buddy's action and mentor requests out of the FAQ
daniilperkin Sep 25, 2026
8f6adc3
Offer flag_to_pm whenever the hire asks for it
DavidLeuter Sep 26, 2026
523c91b
Merge pull request #210 from SprintStartProject/feature/209-faq-buddy…
daniilperkin Sep 27, 2026
bd740fa
Retry grading when a reply leaves an answer ungraded
DavidLeuter Sep 29, 2026
3926ae2
Tell the team buddy that preparing a change is acting for the manager
LinseCed Sep 29, 2026
b596c6f
Only a hire's question is answered with a tool
LinseCed Sep 29, 2026
2a9064a
Merge pull request #213 from SprintStartProject/fix/team-buddy-can-pr…
LinseCed Sep 30, 2026
a1c0147
Route questions about the app to get_app_guide
DavidLeuter Sep 30, 2026
7f63105
Format the app-guide clause
DavidLeuter Sep 30, 2026
3a96fa6
Accept BITBUCKET as a source system
kiranfin Oct 1, 2026
31b2ed0
Accept NOTION as a source system
kiranfin Oct 1, 2026
72f8e68
Merge pull request #208 from SprintStartProject/feature/311-buddy-onb…
DavidLeuter Oct 2, 2026
691cd2f
Move the evidence budget out of the chat agent
daniilperkin Sep 25, 2026
8e592f0
Give Buddy chat's search, filters, reasoning and fence
daniilperkin Sep 25, 2026
4d91d9d
Fix the fence crash, show failed tool calls, and share what the loops…
daniilperkin Sep 28, 2026
86a4d68
Merge pull request #211 from SprintStartProject/feature/206-buddy-sea…
daniilperkin Oct 2, 2026
129fae7
Merge remote-tracking branch 'origin/dev' into feature/buddy-page-kno…
DavidLeuter Oct 3, 2026
ed259dc
Link a how-to's own deep link so the right tab opens
DavidLeuter Oct 3, 2026
8f64d51
Keep the app guide's buttons apart from the chat's own
DavidLeuter Oct 4, 2026
c7836f2
Merge pull request #215 from SprintStartProject/feature/buddy-page-kn…
DavidLeuter Oct 4, 2026
be6925a
Merge pull request #214 from SprintStartProject/bitbucket-connector
kiranfin Oct 4, 2026
8093f64
Merge 'origin/dev' into notion-connector
kiranfin Oct 5, 2026
3a31161
Merge pull request #217 from SprintStartProject/notion-connector
kiranfin Oct 6, 2026
5b5fb89
Remove the chat endpoint and the agent behind it
daniilperkin Oct 6, 2026
5261a67
Re-point the integration tests that drove the chat endpoint
daniilperkin Oct 6, 2026
c6aab0c
Sweep the chat leftovers the removal missed
kiranfin Oct 6, 2026
608d123
Restore project-isolation coverage on the buddy route
kiranfin Oct 6, 2026
5acd4cb
Merge pull request #218 from SprintStartProject/refactor/207-remove-chat
daniilperkin Oct 6, 2026
bdb2b3c
Stream the buddy agent turn live from the AI service
kiranfin Oct 6, 2026
9cb672a
Carry the buddy's reasoning through backend tool round trips
kiranfin Oct 7, 2026
bd6e6f8
Merge pull request #219 from SprintStartProject/ai-streaming-fixes
kiranfin Oct 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ OPENAI_API_KEY=
OPENAI_CHAT_MODEL=
OPENAI_EMBED_MODEL=
OPENAI_VISION_MODEL=
# Fixed dimension for embedding vectors from MRL-capable embedding models
# (e.g. Qwen3 via OpenRouter). Must match the dimension the Chroma collection
# was created with — changing it requires re-creating the collection. Leave
# empty to use the model's native output dimension.
OPENAI_EMBED_DIMENSIONS=
# Optional output limit for streamed chat answers. When a reasoning budget is
# enabled, this value must be greater than OPENAI_REASONING_MAX_TOKENS.
OPENAI_MAX_TOKENS=
Expand All @@ -61,6 +66,15 @@ OPENAI_REASONING_MAX_TOKENS=
# Set to 1 to log each agent's reasoning step (LLM text + tool calls) to stderr.
# AGENT_DEBUG=0

# --- prompt-injection fence (onboarding buddy) ---
# Every user message the buddy sees is wrapped in an HMAC marker; the key is
# drawn per process unless FENCE_KEY is set. A deployment with more than one
# worker or replica must set the same FENCE_KEY everywhere — with different
# keys, each process fences the same text into different bytes, so prompt
# caching stops matching across processes (still safe, just slower). Any
# sufficiently random string; it is hashed to the key.
FENCE_KEY=

# --- ingestion settings ---
# INGEST_CONCURRENCY=8
# INGEST_MAX_CONTENT_LENGTH=500000
Expand Down
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -222,4 +222,8 @@ __marimo__/

data/

opencode.json
opencode.json

# Hermes agent working files (per-developer)
.worktrees/
.worktreeinclude
30 changes: 15 additions & 15 deletions AGENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,25 +37,25 @@ Run lint + type-check + tests before considering a change done.
`src/` layout, by subpackage:

- **`api/`** — FastAPI app (`app.py`), DI (`dependencies.py`), `schemas.py`,
and routes: `health`, `ingest`, `chat`, `title`, `vector_db`, plus
and routes: `health`, `ingest`, `buddy`, `title`, `vector_db`, plus
`onboarding` and `blueprints` (from #98).
- **`agents/`** — `ChatAgent` (`chat_agent.py`) runs one flat tool loop over a
single message list, capped by `_MAX_STEPS`: it searches while the model
asks for searches, then streams the answer from that same conversation.
Tool results carry the retrieved text, so the loop that searched is also the
loop that answers — there is no separate gather and answer phase.
`ChatOrchestrator` (`orchestrator.py`) maps the run's events into the SSE
stream consumed by `/api/v1/chat`. Tools live in `agents/tools/`, registered
via `ToolRegistry`.
- **`agents/`** — the buddy's tool-loop building blocks: tools live in
`agents/tools/` (retrieval, grep, evidence helpers), registered via
`ToolRegistry`, and `artifact_summary.py` backs the summaries endpoint. The
conversation engine itself is `onboarding/buddy_agent.py`: one flat tool
loop over a single message list, capped by `_MAX_STEPS` — it searches while
the model asks for searches, then answers from that same conversation. Tool
results carry the retrieved text, so the loop that searched is also the loop
that answers — there is no separate gather and answer phase.
- **`ingestion/`** — per-filetype parsers (`text_parser`, `pdf_parser`,
`code_parser`, `image_parser`) behind `parser.py`, then `chunker.py` and
`metadata_store.py`.
- **`rag/`** — `retriever.py`, `hybrid.py` (BM25 + vector hybrid retrieval),
`citation.py`, `prompt.py`.
- **`llm/`** — `LLMClient` interface (`base.py`), implemented by
`ollama_client.py`, `openai_client.py`, `anthropic_client.py`.
`split_client.py` lets chat and embeddings use different backends
(`LLM_BACKEND` for chat/generate/vision, `EMBED_BACKEND` for embeddings).
`split_client.py` lets generation and embeddings use different backends
(`LLM_BACKEND` for generate/chat/vision, `EMBED_BACKEND` for embeddings).
Every backend takes a request timeout (`LLM_TIMEOUT_SECONDS`, default 600).
The Anthropic client sets prompt-cache breakpoints on the system prompt and
the last message — see `anthropic_client._user_message` before changing how
Expand All @@ -79,15 +79,15 @@ Run lint + type-check + tests before considering a change done.
- Don't assume Ollama-only: check `llm/base.py`'s `LLMClient` interface when
touching anything that calls an LLM, since backend is configurable per
deployment (`LLM_BACKEND` / `EMBED_BACKEND`).
- Chat is one agent running one loop. Extend it with a new `Tool` in
- The buddy is one agent running one loop. Extend it with a new `Tool` in
`agents/tools/` rather than a sub-agent to delegate to: every tier costs a
serialized round-trip ahead of the user's first token, which is what the
previous orchestrator/synthesis split cost. Tool results must carry their
chunk text — a count-only summary forces a second pass to write the answer.
- **Tools must be read-only and thread-safe.** A turn's tool calls run
concurrently (`ChatAgent._run_tools`), so a tool that mutates shared state
would race. Anything cached across calls needs its own lock, the way
`rag.hybrid.BM25IndexCache` guards its rebuild.
concurrently (`agents.tools.base.run_tool_calls`), so a tool that mutates
shared state would race. Anything cached across calls needs its own lock,
the way `rag.hybrid.BM25IndexCache` guards its rebuild.
- `AGENT_DEBUG=1` logs each agent's reasoning (LLM text + tool calls) to
stderr — useful when debugging agent behavior.
- The onboarding pipeline is intentionally deterministic/staged rather than
Expand Down
36 changes: 21 additions & 15 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,20 +43,22 @@ triggers Docker image publish to `ghcr.io/sprintstartproject/sprintstart-ai`.

- **`api/`** — FastAPI app (`app.py`), DI (`dependencies.py`), `schemas.py`,
SSE helpers (`sse.py`), and route modules under `routes/`.
- **`agents/`** — `ChatAgent` (`chat_agent.py`) runs one flat tool loop over a
single message list: it searches while the model asks, then streams the
answer from that same conversation. Tool results carry the retrieved text,
so the loop that searched is the loop that answers. Tools live in
`agents/tools/`, registered via `ToolRegistry`. `ChatOrchestrator`
(`orchestrator.py`) maps the run's events to SSE for `/api/v1/chat`.
- **`agents/`** — the buddy's tool-loop building blocks: tools live in
`agents/tools/` (retrieval, grep, evidence helpers), registered via
`ToolRegistry`, and `artifact_summary.py` backs the summaries endpoint. The
conversation engine itself is `onboarding/buddy_agent.py`
(`run_agent_turn`): one flat tool loop over a single message list — it
searches while the model asks, then answers from that same conversation.
Tool results carry the retrieved text, so the loop that searched is the
loop that answers. `api/routes/buddy.py` serves it.
- **`ingestion/`** — Per-filetype parsers (`text_parser`, `pdf_parser`,
`code_parser`, `image_parser`) behind `parser.py`, then `chunker.py` and
`metadata_store.py`.
- **`rag/`** — `retriever.py`, `hybrid.py` (BM25 + vector hybrid retrieval
with RRF fusion), `citation.py`, `prompt.py`.
- **`llm/`** — `LLMClient` protocol (`base.py`) with implementations:
`ollama_client.py`, `openai_client.py`, `anthropic_client.py`.
`SplitLLMClient` lets chat and embeddings use different backends
`SplitLLMClient` lets generation and embeddings use different backends
(`LLM_BACKEND` vs `EMBED_BACKEND`). All three take a request timeout
(`LLM_TIMEOUT_SECONDS`, default 600). The Anthropic client sets
prompt-cache breakpoints; caching matches on exact bytes, so read
Expand All @@ -76,10 +78,14 @@ scoping is **fail-closed** — a chunk with no project association is invisible
to all projects rather than visible to all of them. When touching retrieval,
ingest, or an endpoint, keep that property:

- `project_id` is required on `ChatRequest`, `OnboardingPathRequest`,
- `project_id` is required on `OnboardingPathRequest`,
`GenerateBlueprintsRequest`, `KnowledgeGapsRequest` and `FaqGroupRequest`
(all inherit `ProjectScopedRequest` in `api/schemas.py`).
- `RetrievalFilters.project_id` is enforced in **both** halves of hybrid
`project_ids` (plural, required) is enforced on `AssembleOrientationRequest`,
`AssembleDiagramRequest`, and `MineStarterWorkRequest` for multi-project
scoping; `BuddyAgentRequest.project_ids` scopes the buddy's searches the
same way (empty admits nothing).
- `RetrievalFilters.project_id` / `project_ids` is enforced in **both** halves of hybrid
retrieval — `where_filter_for_chroma()` for the vector side and
`matches_retrieval_filters()` for the BM25/in-memory side (`rag/filters.py`).
Anything that scans `all_chunks_without_embeddings()` directly (the `grep`
Expand All @@ -100,13 +106,13 @@ ingest, or an endpoint, keep that property:
- Don't assume Ollama-only: check `llm/base.py`'s `LLMClient` protocol when
touching LLM calls. Backend is configurable per deployment via
`LLM_BACKEND` / `EMBED_BACKEND`.
- Chat is one agent, one loop. Give it a new `Tool` in `agents/tools/` rather
than a sub-agent to delegate to: every tier costs a serialized round-trip
ahead of the user's first token. Tool results must carry their chunk text —
a count-only summary forces a second pass to write the answer.
- The buddy is one agent, one loop. Give it a new `Tool` in `agents/tools/`
rather than a sub-agent to delegate to: every tier costs a serialized
round-trip ahead of the user's first token. Tool results must carry their
chunk text — a count-only summary forces a second pass to write the answer.
- Tools must be read-only and thread-safe: a turn's tool calls run
concurrently (`ChatAgent._run_tools`). Cache across calls only behind a lock,
as `rag.hybrid.BM25IndexCache` does.
concurrently (`agents.tools.base.run_tool_calls`). Cache across calls only
behind a lock, as `rag.hybrid.BM25IndexCache` does.
- `AGENT_DEBUG=1` logs each agent's reasoning (LLM text + tool calls) to
stderr — useful when debugging agent behavior.
- The onboarding pipeline is intentionally deterministic/staged rather than
Expand Down
6 changes: 6 additions & 0 deletions docs/faq-grouping-concept.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,12 @@ Supporting decisions:
- **Classification is idempotent** via the source message id. Events can be redelivered, and
counting a message twice would quietly corrupt the frequency ranking the whole panel is ordered
by.
- **The classifier, not the caller, decides what is an FAQ question.** Questions to Buddy (the
project's assistant, Wiki#319) include requests to act ("move my card to done"), questions about
the asker's own onboarding ("what should I work on next?") and context-only follow-ups ("and the
second one?") next to documentation questions. All of them are sent; `relevant = false` drops
them, as it drops smalltalk. Classify and the full rebuild share one list of these categories
(`insights.faq.NON_FAQ_KINDS`), so a rebuild keeps out exactly what the live path kept out.
- **Retrieval only runs for a new entry.** An existing one already carries the documents that
answer it.
- **Redaction is folded into the same call**, rather than a second round-trip per message.
Expand Down
4 changes: 2 additions & 2 deletions scripts/_client.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Shared HTTP client for the SprintStart AI terminal tools.

All the subcommands (chat, ingest, onboard, corpus) talk to the same running
All the subcommands (ingest, onboard, corpus) talk to the same running
service, so the connection handling, health check, SSE decoding, error
reporting, and document ingestion live here once. The offline chunk inspector
(`chunk_inspector_cli.py`) intentionally does not use this — it parses local
Expand Down Expand Up @@ -183,7 +183,7 @@ def detail(response: httpx.Response) -> str:
except (json.JSONDecodeError, ValueError):
return response.text

# --- ingestion (shared by the chat REPL and the ingest subcommand) ----
# --- ingestion (used by the ingest subcommand) -----------------------

def ingest_path(self, raw_path: str, artifact_id: str | None) -> None:
path = Path(raw_path).expanduser()
Expand Down
186 changes: 0 additions & 186 deletions scripts/chat_cli.py

This file was deleted.

Loading
Loading