Ask code, not documentation - Harvesting knowledge from project source using Agents.
| Chat | Explore | Document |
|---|---|---|
![]() |
![]() |
![]() |
Harvest turns versioned source code repositories into a queryable knowledge graph. Point it at a list of Git repositories, let it ingest every tagged version, then ask natural-language questions through the chat interface or the HTTP API.
┌─────────────────────┐ ┌──────────────────────┐ ┌──────────────┐
│ knowledge-harvester │────▶│ Neo4j graph │────▶│ knowledge- │
│ (Rust CLI / daemon)│ │ functions, classes, │ │ server │
│ git + tree-sitter │ │ calls, imports, … │ │ (HTTP + SSE) │
└──────────┬──────────┘ └──────────────────────┘ └──────┬───────┘
│ document │
▼ ┌────────▼───────┐
Diataxis docs │ web-ui │
(markdown files)─────────────────────────────────▶│ (Vite / JS) │
└────────────────┘
│
┌────────▼───────┐
│ harvest-agent │
│ (Rust daemon) │
└────────────────┘
-
Harvester clones each repository, walks provided git refs, and parses the source with tree-sitter. Functions, classes, imports, call edges, and class relationships (inherits, implements, embeds, uses) are written to Neo4j. Each
(repo, version)pair is an atomic unit — safe to interrupt and re-run. A separatedocumentcommand generates Diataxis-structured documentation for any ingested version using an LLM. -
Server exposes a REST + SSE API. A query triggers an agentic loop: the LLM classifies intent (conversational / research / action / hybrid), then calls graph tools (search, source retrieval, call-graph traversal, custom Cypher), machine tools (agent commands, terraform plan/apply/destroy, port-forwards), skill, artifact, and deployment tools until it has enough context, then returns a structured answer with inline
[repo:version:file:line]citations. The server also serves the full symbol graph for any(repo, version)pair and static Diataxis documentation pages generated by the harvester. It includes full authentication (JWT + optional Google OAuth and OIDC SSO), project/group management, conversation history with compaction, an IaC deployment pipeline (design → provision → execution plan → runs), a skill store, and an overview pipeline that generates AI-powered environment status dashboards. Multiple LLM providers can be configured with priority-based fallback and a chat-page model picker. -
Web UI is a Vue 3 SPA providing a streaming chat interface (with Mermaid diagrams, inline graph snippets, ask-user prompts, and confirm-action gating), an interactive symbol graph explorer, a documentation browser, project and agent management, per-project environment overviews, and dedicated Deploy, Design, Artifacts, and Skills views. An interactive terminal console can be opened on any connected agent.
-
Agent daemon (
harvest-agent) runs on any machine, connects to the server via SSE, and executes bash commands on behalf of the agentic loop. It also runs Terraform/Terragrunt bundles (plan/apply/destroy with streamed output), serves interactive shell sessions (xterm.js over WebSocket), and opens reverse tunnels so the server can reach ports on the agent's machine. The LLM can uselist_agents,run_command, and the terraform/port-forward tools to inspect and control connected machines.
harvest/
├── knowledge-harvester/ # Rust CLI — ingests repos into Neo4j
│ ├── src/
│ │ ├── documentation/ # LLM-driven Diataxis doc generation pipeline
│ │ ├── graph/ # graph model and Neo4j writer
│ │ └── parser/ # tree-sitter per-language parsers
│ └── harvester.toml # example config
├── knowledge-server/ # Rust HTTP server — answers questions
│ ├── src/
│ │ ├── agent/ # agentic loop + all tool definitions (graph,
│ │ │ # machine, skill, lxd, port-forward, terraform,
│ │ │ # artifact, deployment) and prompts
│ │ ├── api/ # axum routes
│ │ ├── artifacts/ # generated artifact store (terraform bundles, docs)
│ │ ├── auth/ # JWT, Google OAuth, OIDC, password hashing
│ │ ├── conversations/ # user conversation history
│ │ ├── deployments/ # IaC deployment pipeline (design, provision, runs)
│ │ ├── llm/ # LLM provider adapters (Anthropic, Gemini,
│ │ │ # OpenAI-compat) + fallback routing + retry
│ │ ├── lxd/ # LXD REST client + agent provisioning
│ │ ├── machines/ # agent registry, SSE, console/tunnel, port-forwards
│ │ ├── overview/ # environment status pipeline
│ │ ├── projects/ # project/group CRUD
│ │ └── skills/ # global + per-project skill store
│ ├── skills/ # built-in skill markdown (juju, lxd, ceph, …)
│ └── server.toml # example config
├── agent/ # Rust daemon — executes commands on remote machines
│ └── src/ # (also runs terraform, console, and tunnel sessions)
├── web-ui/ # Vue 3 SPA (Vite + Vitest, Pinia, Vue Router)
│ ├── src/
│ └── tests/
├── documentation/
│ └── developer/ # architecture, harvester, server, dev-setup docs
├── docker-compose.yml # Neo4j + server + web-ui (all-in-one)
└── Cargo.toml # Cargo workspace
docker compose up -d
# Neo4j browser: http://localhost:7474 (neo4j / devpassword)
docker-compose.ymlalso definesserverandweb-uiservices. To run the whole stack at once, fill inLLM_API_KEYandJWT_SECRETin the.envfile, and rundocker compose up -d. The steps below build and run each component from source instead.
Edit knowledge-harvester/harvester.toml:
[neo4j]
uri = "bolt://localhost:7687"
user = "neo4j"
password = "devpassword"
[storage]
clone_root = "/tmp/harvest-repos"
[[repositories]]
name = "my-repo"
url = "https://github.com/owner/my-repo.git"
# Optional: pin specific refs instead of ingesting all tags
# refs = ["v1.0", "v2.0", "main"]Run the harvester:
cd knowledge-harvester
RUST_LOG=info cargo run -- --config harvester.toml runTo re-process all previously ingested versions:
RUST_LOG=info cargo run -- --config harvester.toml reingestEdit knowledge-server/server.toml:
[server]
host = "127.0.0.1"
port = 8080
[neo4j]
uri = "bolt://localhost:7687"
user = "neo4j"
password = "devpassword"
[auth]
jwt_secret = "change-me-in-production"
allow_local_login = true
# Optional: Google OAuth
# [auth.google]
# client_id = "..."
# client_secret = "..."
# redirect_uri = "http://localhost:8080/auth/google/callback"
# Optional: OIDC SSO (e.g. Ubuntu One, Dex, Keycloak)
# [auth.oidc]
# issuer_url = "https://login.example.com"
# client_id = "harvest"
# client_secret = "..."
# redirect_uri = "http://localhost:8080/auth/oidc/callback"
# display_name = "Single Sign-On"
# Agent behaviour (all optional, defaults shown)
[agent]
max_iterations = 20
compaction_threshold_chars = 40000
compaction_keep_last = 6
# One or more LLM providers. Define as many [[llm]] blocks as you need;
# on rate-limit errors they are tried in ascending priority order.
# `expose_to_ui = false` keeps a provider as failover only (hidden from
# the chat page's model picker); `models` restricts the picker to a curated
# allowlist; `name` is the display name shown in the picker.
# Anthropic Claude
[[llm]]
provider = "anthropic"
model = "claude-sonnet-4-6"
api_key = "sk-ant-..."
priority = 0
# — or — Google Gemini
# [[llm]]
# provider = "gemini"
# model = "gemini-2.5-flash"
# api_key = "AIza..."
# — or — OpenAI-compatible (Groq, Ollama, etc.)
# [[llm]]
# provider = "openai-compatible"
# base_url = "https://api.groq.com/openai/v1"
# api_key = "gsk_..."
# model = "llama-3.3-70b-versatile"
# priority = 1
# Optional: expose the harvest-agent binary for download
# [agents]
# binary_path = "/usr/local/bin/harvest-agent"
# public_url = "https://harvest.example.com"cd knowledge-server
RUST_LOG=info cargo run -- --config server.toml
# Listening on 127.0.0.1:8080The first user to register becomes an admin.
To generate Diataxis-structured documentation for an ingested version, add [llm] and [documentation] sections to harvester.toml:
[llm]
provider = "anthropic"
model = "claude-sonnet-4-6"
api_key = "sk-ant-..."
[documentation]
docs_dir = "/tmp/harvest-docs"Then run the document command for a specific repo:version:
RUST_LOG=info cargo run -- --config harvester.toml document my-repo:v1.2.0Point the server at the same docs_dir (via server.toml) to serve the generated pages through the web UI.
cd web-ui
npm install
npm run dev
# Open http://localhost:5173The Vite dev server proxies all API calls to localhost:8080 automatically.
All routes except /health, /auth/*, and agent endpoints require a JWT session cookie obtained via POST /auth/login or POST /auth/register. The cookie is set automatically by the browser. In addition to local password login, Google OAuth 2.0 and/or OIDC SSO can be enabled in server.toml.
# Register (first user becomes admin)
curl -c cookies.txt -s http://localhost:8080/auth/register \
-H 'Content-Type: application/json' \
-d '{"email":"you@example.com","name":"You","password":"secret123"}'
# Login
curl -c cookies.txt -s http://localhost:8080/auth/login \
-H 'Content-Type: application/json' \
-d '{"email":"you@example.com","password":"secret123"}'Ask a question, get a complete JSON response.
curl -b cookies.txt -s http://localhost:8080/query \
-H 'Content-Type: application/json' \
-d '{"query": "How does the retry logic work?"}' | jq .{
"answer": "The retry logic lives in `llm/retry.rs` …",
"sources": [
{ "repo": "my-repo", "version": "v1.2.0", "file": "src/llm/retry.rs", "line": 12 }
],
"tool_calls_made": 4,
"provider_used": { "provider_id": "anthropic-0", "kind": "anthropic", "model": "claude-sonnet-4-6" }
}Same payload, streams Server-Sent Events so you can display intent, thinking, and tool calls as they happen:
| Event | Payload |
|---|---|
intent |
{type, mode} — conversational / research / action / hybrid |
phase |
{type, label} — human-readable phase derived from the tool calls |
thinking_delta |
{type, text} — reasoning-model thinking tokens |
text_delta |
{type, text} — streamed answer text |
tool_call |
{type, name, input} |
tool_result |
{type, name, preview} |
question |
{type, question, choices} — the LLM called ask_user |
confirm_action |
{type, id, name, input, description} — a destructive tool awaits approval |
done |
{type, answer, sources, tool_calls_made, provider_used} |
error |
{type, message} |
List all ingested repositories and their available versions.
Return the full symbol graph for a (repo, version) pair as JSON — nodes (functions and classes) and edges (calls, contains, inherits, implements, embeds, uses). Results are served from an in-memory cache pre-warmed at startup. Large graphs are truncated to 1 500 nodes / 6 000 edges with a "truncated": true flag.
Fetch the source text, signature, and line range for a single symbol.
Return the documentation index (JSON) listing all generated pages organised by Diataxis section.
Serve a single documentation page as text/markdown.
Returns {"status": "ok"}.
Lists the LLM providers visible to the chat page's model picker (those with expose_to_ui = true), with their discovered/allow-listed models.
List or fetch a global skill (markdown with YAML frontmatter). Global skills are seeded at startup (juju, lxd, ceph, canonical-k8s, landscape, openstack) and managed via the admin routes below.
Fetch an artifact's metadata or download its raw content. Artifacts are generated by the agent (terraform bundles, design docs, guides) or uploaded as deployment context.
GET /groups — list groups the user belongs to
GET /projects — list accessible projects
POST /projects — create a project
GET /projects/:pid — get project details
PUT /projects/:pid — update project name/description
DELETE /projects/:pid — delete project (admin or creator)
GET /projects/:pid/conversations — list conversations
POST /projects/:pid/conversations — create a conversation
GET /projects/:pid/conversations/:cid — get conversation with messages
PUT /projects/:pid/conversations/:cid — update title and messages
DELETE /projects/:pid/conversations/:cid — delete conversation
POST /projects/:pid/conversations/:cid/confirm-action/resume — resume after a confirm_action
POST /projects/:pid/query — non-streaming query with history
POST /projects/:pid/query/stream — streaming query (fire-and-forget; events via SSE)
GET /projects/:pid/events — SSE channel for real-time collaboration events
GET /projects/:pid/skills — list skills available to the project (global + project-scoped)
POST /projects/:pid/skills — create a project-scoped skill
GET /projects/:pid/skills/:sid — get a project skill
PUT /projects/:pid/skills/:sid — update a project skill
DELETE /projects/:pid/skills/:sid — delete a project skill
GET /projects/:pid/artifacts — list project artifacts
POST /projects/:pid/artifacts — create/upload an artifact
GET /projects/:pid/secrets — list secret names (not values)
POST /projects/:pid/secrets — upsert a secret by name
DELETE /projects/:pid/secrets/:name — delete a secret
The LLM tools list_secrets, get_secret, and save_secret give the agent controlled access to the project's secret store.
GET /projects/:pid/overview — get current environment status HTML + metadata
GET /projects/:pid/overview/events — SSE stream for in-progress generation
POST /projects/:pid/overview/regenerate — trigger a new overview generation
A full IaC deployment pipeline per project. Each deployment carries a design doc, a Terraform/Terragrunt bundle, an optional guide, context artifacts, and a topologically-sorted execution plan. Deploys/redeploys/destroys run the bundle on a connected agent and record a DeploymentRun with captured output.
GET /projects/:pid/deployment — get the project's current deployment
GET /projects/:pid/deployments — list deployments
POST /projects/:pid/deployments — create a deployment
GET /projects/:pid/deployments/:did — get deployment detail
PATCH /projects/:pid/deployments/:did — update deployment metadata
DELETE /projects/:pid/deployments/:did — delete a deployment
POST /projects/:pid/deployments/:did/deploy — run the bundle (apply)
POST /projects/:pid/deployments/:did/redeploy — re-apply the bundle
POST /projects/:pid/deployments/:did/destroy — destroy the infrastructure
GET /projects/:pid/deployments/:did/runs — list recorded runs
POST /projects/:pid/deployments/:did/environment/questions — interview the LLM for env requirements
POST /projects/:pid/deployments/:did/design/generate — generate the design doc
POST /projects/:pid/deployments/:did/design/decisions — capture design decisions
POST /projects/:pid/deployments/:did/design/revise — revise the design
POST /projects/:pid/deployments/:did/provision/generate — generate the terraform bundle
POST /projects/:pid/deployments/:did/provision/propose-change — propose a bundle edit (staged)
POST /projects/:pid/deployments/:did/provision/apply-change — apply a proposed bundle edit
POST /projects/:pid/deployments/:did/context-artifacts — add a context artifact
POST /projects/:pid/deployments/:did/context-artifacts/link — link an existing artifact
DELETE /projects/:pid/deployments/:did/context-artifacts/:aid — remove a context artifact
GET /projects/:pid/deployments/:did/proposals — list proposed changes
POST /projects/:pid/deployments/:did/proposals — propose an artifact change
POST /projects/:pid/deployments/:did/proposals/:propid/approve — approve a proposal
POST /projects/:pid/deployments/:did/proposals/:propid/discard — discard a proposal
GET /projects/:pid/deployments/:did/execution-plan — get the execution plan
POST /projects/:pid/deployments/:did/execution-plan — set the execution plan
POST /projects/:pid/deployments/:did/run-dag — execute the plan's DAG
Reusable product templates (group-scoped) that pre-seed a deployment's design and bundle.
GET /groups/:gid/templates — list templates in a group
POST /groups/:gid/templates — create a template
GET /groups/:gid/templates/:tid — get a template
PUT /groups/:gid/templates/:tid — update a template
DELETE /groups/:gid/templates/:tid — delete a template
GET /agents/:pid/install.sh — generate install script for a project
GET /agents/binary/harvest-agent — download the agent binary
GET /projects/:pid/agents — list connected agents
DELETE /projects/:pid/agents/:aid — remove an agent (deletes the LXD container too, if it has one)
POST /projects/:pid/agents/:aid/execute — run a bash command on an agent
POST /projects/:pid/agents/:aid/terraform — run a terraform/terragrunt plan|apply|destroy bundle on an agent
GET /projects/:pid/agents/:aid/console — open an interactive shell (xterm.js over WebSocket)
POST /projects/:pid/agents/:aid/start — start an LXD-managed agent container
POST /projects/:pid/agents/:aid/stop — stop an LXD-managed agent container
POST /projects/:pid/agents/:aid/restart — restart an LXD-managed agent container
POST /projects/:pid/agents/rotate-install-token — rotate the install token
GET /projects/:pid/agents/flavors — list LXD container sizes (requires [lxd] config)
POST /projects/:pid/agents/lxd — provision a Harvest-managed agent on LXD (requires [lxd] config)
GET /projects/:pid/agents/:aid/port-forwards — list port forwards for an agent
POST /projects/:pid/agents/:aid/port-forwards — create a port forward (server port → agent port)
PUT /projects/:pid/agents/:aid/port-forwards/:fid — update a port forward
DELETE /projects/:pid/agents/:aid/port-forwards/:fid — delete a port forward
Agent-facing endpoints (token-authenticated, not JWT):
GET /agent/events — SSE stream the agent connects to (receives Execute, RunTerraform, OpenShell, OpenTunnel, Uninstall)
POST /agent/results — agent posts a finished command/terraform result
POST /agent/output — agent posts a streamed stdout/stderr line
POST /agent/ping — agent heartbeat
GET /agent/console/:session_id — WebSocket upgrade for an interactive console session
GET /agent/tunnel/:session_id — WebSocket upgrade for a reverse tunnel session
Port forwards expose a service running on an agent's machine through the server via a reverse tunnel; the server also proxies arbitrary HTTP paths under /agents/:agent_id/:route_name/... to the forwarded port.
Agents can be added two ways: install the daemon on a machine you already have, or — if the server has an [lxd] section configured — let Harvest provision and manage a container for you on an LXD cluster. The web UI's Agents page offers both options once LXD is configured; see server.md for the full provisioning flow.
GET /admin/users — list all users (admin only)
PUT /admin/users/:id/role — set a user's role (admin/regular)
PUT /admin/users/:id/groups — set a user's group memberships
GET /admin/groups — list all groups
POST /admin/groups — create a group
DELETE /admin/groups/:id — delete a group
PUT /admin/groups/:id/default — set a user's default group
POST /admin/skills — create a global skill
PUT /admin/skills/:id — update a global skill
DELETE /admin/skills/:id — delete a global skill
The standard project agent has access to these tools. Each maps to one or more Neo4j queries or an agent RPC.
| Tool | Description |
|---|---|
list_repositories |
All repos and their ingested versions |
search_symbols |
Full-text search for functions/classes by name |
get_symbol_source |
Full source text of a specific function or class |
get_file_symbols |
All symbols defined in a file |
find_callers |
Functions that call a given function |
find_callees |
Functions called by a given function |
get_imports |
Import declarations for a file |
compare_symbol_across_versions |
Source diff for a symbol between two versions |
run_cypher |
Arbitrary read-only Cypher for custom traversals |
| Tool | Description |
|---|---|
list_agents |
List connected agent machines in the project |
run_command |
Execute a bash command on a connected agent |
list_skills |
List skills available to the project (global + project-scoped) |
load_skill |
Load a skill's full markdown body into context |
create_lxd_agent |
Provision a new LXD-managed agent (requires [lxd]) |
delete_agent |
Remove an agent (and its LXD container, if any) |
list_port_forwards |
List port forwards for agents in the project |
create_port_forward |
Open a reverse-tunnelled port forward to an agent |
update_port_forward |
Update an existing port forward |
delete_port_forward |
Close and remove a port forward |
generate_artifact |
Generate an artifact (terraform bundle, design doc, guide) and store it |
run_terraform_plan |
Run `terraform |
run_terraform_apply |
Run `terraform |
run_terraform_destroy |
Run `terraform |
| Tool | Description |
|---|---|
list_secrets |
List secret names stored for the project |
get_secret |
Retrieve a secret value by name |
save_secret |
Store or update a secret value |
| Tool | Description |
|---|---|
ask_user |
Present a clarifying question with 2–4 choices to the user (always available) |
These are added to a deployment-scoped agent (build_for_deployment) and are not part of the standard project chat agent:
| Tool | Description |
|---|---|
link_deployment_artifact |
Attach an artifact to a deployment |
update_product_template |
Save the current design/bundle as a reusable group template |
set_execution_plan |
Define the ordered DAG of deploy/destroy steps |
read_provision_bundle |
Read the current terraform bundle for a deployment |
The web UI is a Vue 3 single-page application (Vite, Pinia, Vue Router) styled with Canonical Vanilla Framework.
Chat
- Streaming responses — intent badge, phase labels, reasoning-model thinking deltas, and streamed answer text
- Step timeline — each tool invocation appears as a collapsible step with an AI-generated plain-English description, tool name, raw inputs, and result preview
- Mermaid diagrams — fenced
```mermaidblocks render as visual diagrams - Inline graph snippets — fenced
harvest-graphblocks render as mini interactive graphs; clicking a node opens its source - Inline symbol graphs — answers that reference specific symbols include a mini interactive graph showing relationships
- Markdown answers — rendered with syntax-highlighted code blocks and copy-to-clipboard buttons
- Source citations — inline
[repo:version:file:line]markers become amber chips linking to the source file; a sources panel lists them all - Attachments — attach images and PDFs to a query; the LLM receives them as vision/document input
- ask_user prompts — when the agent needs clarification, a choice card is rendered inline
- Confirm-action gating — destructive tool calls pause and surface an approval card before executing
- Model picker — switch between exposed LLM providers and their allow-listed models
Graph explorer
- Interactive symbol graph — browse the full call and relationship graph for any
(repo, version)pair, rendered with Cytoscape.js and an off-thread fcose layout - Symbol search — highlight matching nodes instantly; AI search mode finds semantically related nodes
- Source panel — click any node to see its signature and full source inline
Documentation
- Diataxis browser — read AI-generated documentation organised into Tutorials, How-to Guides, Explanations, and Reference sections for any ingested version
Deploy & Design
- Deployments — create and manage IaC deployments; generate design docs and terraform bundles with the agent; run plan/apply/destroy with streamed output; edit and approve proposed bundle changes
- Design — capture environment requirements through an LLM interview, generate and revise the design doc, and record design decisions
Artifacts, Skills
- Artifacts — browse, download, and manage generated terraform bundles, design docs, and guides
- Skills — browse global and project skills (markdown playbooks the agent loads on demand)
Projects
- Project workspaces — group conversations by project; share a project with your team via group membership
- Real-time collaboration — presence indicators, typing lock, and live event streaming via SSE so multiple users can see what is happening
- Conversation history — all turns are persisted server-side with automatic compaction when histories grow large
Agents
- Remote agent management — install
harvest-agenton any machine with a one-liner; manage connected agents from the web UI - Interactive console — open an xterm.js terminal on any connected agent
- LLM-driven automation — the project agent can run bash commands and terraform on connected machines, open port forwards, and store discovered credentials in the project secret store
Overview
- Environment status dashboard — AI-generated HTML status card summarising the project's infrastructure, based on conversation history and real-time agent tool calls
Shell
- Dark / light / auto theme — toggle in the sidebar; persists across reloads; auto follows the OS setting with no flash on reload
- Responsive navigation — Vanilla Framework application shell with collapsible sidebar for mobile
| Concern | Choice |
|---|---|
| Harvester language | Rust |
| Server language | Rust |
| Agent daemon language | Rust |
| HTTP framework | axum |
| Code parsing | tree-sitter |
| Graph database | Neo4j 5 Community |
| Neo4j Rust driver | neo4rs |
| LLM providers | Claude (Anthropic) · Gemini (Google) · OpenAI-compatible (Groq, Ollama, …) |
| LLM routing | priority-based fallback across multiple providers |
| Streaming | Server-Sent Events (axum SSE) + WebSockets (console/tunnel) |
| Authentication | JWT (cookie) + optional Google OAuth 2.0 + optional OIDC SSO |
| Web UI framework | Vue 3 + Pinia + Vue Router |
| Web UI build | Vite |
| Web UI tests | Vitest (jsdom) |
| Terminal | xterm.js (agent consoles) |
| Graph rendering | Cytoscape.js + fcose layout |
| Diagrams | Mermaid |
| CSS framework | Canonical Vanilla Framework |
| Async runtime | tokio |
| Configuration | TOML |
# Rust unit + integration tests (no Docker needed)
cargo test
# Rust Docker-gated tests (Neo4j testcontainers)
cargo test -- --include-ignored
# Web UI tests
cd web-ui && npm testDetailed documentation lives under documentation/developer/:
architecture.md— system design and component overviewharvester.md— pipeline, graph schema, tree-sitter integrationserver.md— API reference, LLM provider config, agentic loopdev-setup.md— step-by-step local development setupsnap.md— installing and configuring the Harvest snap
The web UI source under web-ui/src/ (views, components, stores, composables) is the authoritative reference for the SPA.


