Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ extensions_config.json
coverage.xml
coverage/
.deer-flow/
.flowlens-agentops/
.claude/
skills/custom/*
logs/
Expand Down
61 changes: 58 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ English | [中文](./README_zh.md)

![FlowLens AgentOps dashboard](./docs/assets/flowlens-agentops-dashboard.png)

[Pages Demo](https://try1004.github.io/FlowLens-AgentOps/) | [Quick Start](#quick-start) | [Architecture](./docs/architecture.md)
[Pages Demo](https://try1004.github.io/FlowLens-AgentOps/) | [Quick Start](#quick-start) | [Architecture](./docs/architecture.md) | [Dual MCP Validation](./docs/mcp-validation-guide.md) | [Maintainer Handoff](./now.md)

> Built on [DeerFlow 2.0](https://github.com/bytedance/deer-flow) under the MIT License. DeerFlow provides the Agent runtime, tools, subagents, memory, MCP, sandbox, and application foundation. FlowLens adds the diagnostics and AgentOps product layer described below.

Expand All @@ -32,6 +32,7 @@ A checkpoint can restore state, but it does not explain why a multi-agent run fa
- **Root Cause**: rule-based primary cause, evidence links, contributing failures, and remediation guidance.
- **Replay mode**: success, tool error, subagent timeout, guardrail denial, checkpoint rollback, MCP auth error, and memory error.
- **Connected mode**: reads the real DeerFlow Gateway and RunEventStore through owner-checked APIs.
- **Dual MCP validation**: internal SQLite Analytics and FlowLens Inspector services demonstrate bounded read-only data access and user-bound runtime delegation.
- **Portable delivery**: Next.js route, Vite static build, Docker/Nginx demo, CI, and GitHub Pages workflow.

<img src="./docs/assets/flowlens-agentops-mobile.png" width="280" alt="FlowLens AgentOps mobile Root Cause view" />
Expand All @@ -44,6 +45,14 @@ The diagnostics path is read-only: it does not mutate checkpoints, prompts, RunR

## Quick Start

### Choose a mode

| Mode | What it proves | Requirements | URL |
| --- | --- | --- | --- |
| Replay | Dashboard interactions and deterministic synthetic failure scenarios | Node.js or Docker; no model key | `http://localhost:5173` or `http://localhost:4173` |
| Connected Runtime | Real DeerFlow runs emitted into the AgentOps event pipeline | Normal DeerFlow configuration, account, and model provider | `http://localhost:2026/agentops` |
| Connected + dual MCP | Runtime delegation, read-only SQLite Analytics MCP, and FlowLens Inspector MCP | Docker Desktop plus the Connected Runtime requirements | `http://localhost:2027/agentops` |

### No-key Replay with pnpm

```bash
Expand All @@ -66,6 +75,14 @@ make flowlens-demo

Open `http://localhost:4173`, then stop it with `make flowlens-stop`.

On Windows without `make`, run the Compose command directly:

```powershell
docker compose -f docker/docker-compose-flowlens-demo.yaml up --build
```

Press `Ctrl+C` to stop the foreground Replay container.

### Connected DeerFlow runtime

```bash
Expand All @@ -75,6 +92,39 @@ make up

Open `http://localhost:2026/agentops`. Connected mode requires the normal DeerFlow configuration, authentication, and at least one configured model provider. It uses the same React components and Zod contracts as Replay mode.

### Connected runtime with dual MCP services

The development stack runs two internal-only Streamable HTTP MCP services:

- **SQLite Analytics MCP** exposes schema inspection and `SELECT`-only local demonstration-data queries.
- **FlowLens Inspector MCP** queries owner-checked diagnostic data through the Gateway.

Schemas are discovered with a restricted service token. A real tool invocation receives a short-lived token bound to the current `user_id` and `run_id`; the model never receives that token.

From the repository root in PowerShell:

```powershell
$env:DEER_FLOW_ROOT = (Get-Location).Path
$env:HOME = $env:USERPROFILE
$env:DEER_FLOW_CONTAINER_PREFIX = "flowlens-agentops"
$env:DEER_FLOW_HTTP_PORT = "2027"

docker compose -p flowlens-agentops `
-f docker/docker-compose-dev.yaml `
-f docker/docker-compose-agentops-isolated.yaml `
up -d --build --remove-orphans frontend gateway nginx sqlite-analytics flowlens-inspector

Invoke-RestMethod http://localhost:2027/health
```

Open `http://localhost:2027/`, sign in, then open `http://localhost:2027/agentops`. To exercise the real SQLite path, start a new chat and send:

```text
Use the SQLite Analytics MCP to inspect the available schema, describe the agent_runs table, then count runs by status. Execute read-only SQL only, state the SQL used, and label the result as local demonstration data.
```

The selected Run should show MCP events in Timeline and an `MCP > 0` metric. The [Dual MCP Validation Guide](./docs/mcp-validation-guide.md) and [Chinese end-to-end runbook](./docs/flowlens-dual-mcp-test-runbook.zh-CN.md) cover expected evidence, policy-rejection cases, and shutdown.

## Diagnostic APIs

| Method | Endpoint | Purpose |
Expand Down Expand Up @@ -125,8 +175,10 @@ FlowLens does not claim authorship of the DeerFlow runtime modules. See [NOTICE]

| Gate | Result |
| ---------------------------------- | ------------------------------------------------------------------ |
| Focused backend FlowLens/API suite | 53 tests passed |
| Frontend Vitest suite | 219 tests passed |
| Focused backend FlowLens/API suite | 58 tests passed on 2026-07-31 |
| Frontend Vitest suite | 232 tests passed on 2026-07-31 |
| Frontend quality gate | ESLint, TypeScript, Next production build, and Vite Replay build passed on 2026-07-31 |
| Real MCP integration probe | `ToolNode -> delegated OAuth token -> SQLite schema tool` completed locally |
| Next and static Replay Playwright | 5 tests passed across route, desktop, tablet, and mobile workflows |
| Secret scan | No high-confidence credentials in Git-tracked files |
| In-process diagnostics benchmark | 2,000 events, 20 iterations, median 24.7578 ms, P95 36.5606 ms |
Expand Down Expand Up @@ -155,6 +207,9 @@ The machine-readable result is committed at [flowlens-benchmark.json](./docs/ass
- [Event schema and instrumentation](./docs/event-schema.md)
- [Failure attribution rules](./docs/failure-attribution.md)
- [Replay and Connected demo guide](./docs/demo-guide.md)
- [Dual MCP validation guide](./docs/mcp-validation-guide.md)
- [Chinese dual MCP end-to-end runbook](./docs/flowlens-dual-mcp-test-runbook.zh-CN.md)
- [Maintainer handoff and next steps](./now.md)
- [Contributing](./CONTRIBUTING.md)
- [Changelog](./CHANGELOG.md)

Expand Down
7 changes: 7 additions & 0 deletions backend/app/gateway/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
channels,
feedback,
mcp,
mcp_tokens,
memory,
models,
runs,
Expand Down Expand Up @@ -178,6 +179,11 @@ async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
logger.exception(error_msg)
raise RuntimeError(error_msg) from e
config = get_gateway_config()
from app.gateway.auth.platform_tokens import (
ensure_platform_tokens_configured_if_enabled,
)

ensure_platform_tokens_configured_if_enabled()
logger.info(f"Starting API Gateway on {config.host}:{config.port}")

# Pre-warm tiktoken encoding cache so the first memory-injection request
Expand Down Expand Up @@ -385,6 +391,7 @@ def create_app() -> FastAPI:

# Auth API is mounted at /api/v1/auth
app.include_router(auth.router)
app.include_router(mcp_tokens.router)

# Feedback API is mounted at /api/threads/{thread_id}/runs/{run_id}/feedback
app.include_router(feedback.router)
Expand Down
1 change: 1 addition & 0 deletions backend/app/gateway/auth/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class User(BaseModel):
model_config = ConfigDict(from_attributes=True)

id: UUID = Field(default_factory=uuid4, description="Primary key")
tenant_id: str = Field(default="default", description="Owning tenant")
email: EmailStr = Field(..., description="Unique email address")
password_hash: str | None = Field(None, description="bcrypt hash, nullable for OAuth users")
system_role: Literal["admin", "user"] = Field(default="user")
Expand Down
Loading
Loading