# Layer
**A local product-decision memory system for AI coding agents.**
[GitHub](https://github.com/Pulko/layer) · `cargo install decision_cockpit --bin mcp`
Layer stores documents, decisions, assumptions, evidence, drift signals, and memos. An MCP-capable agent proposes structured **candidates** and **signals**; you review and accept them in a built-in dashboard.
> The app is not the agent. It is the agent's product-memory operating system.
```
Cursor / MCP client
│
▼
┌─────────┐ ┌──────────────────┐
│ mcp │────▶│ Postgres (Docker)│
│ binary │ └──────────────────┘
└────┬────┘
│ http://localhost:3000
▼
embedded dashboard
```
One self-contained binary includes the UI, API, MCP server, and Docker Compose definition. No separate frontend deploy. No env-file required.
---
## Install
**Requirements:** [Docker Desktop](https://www.docker.com/products/docker-desktop/) (running).
```bash
cargo install decision_cockpit --bin mcp
```
The crates.io package is `decision_cockpit`; the installed binary is `mcp` (typically `~/.cargo/bin/mcp`).
### Connect to Cursor
Add to `.cursor/mcp.json` (project or global):
```json
{
"mcpServers": {
"layer": {
"command": "/Users/you/.cargo/bin/mcp"
}
}
}
```
Use the full path to your `mcp` binary. Then enable the server in **Cursor Settings → MCP**.
On first connect the binary will:
1. Start Postgres via embedded Docker Compose (or reuse an existing container)
2. Run database migrations
3. Serve the review dashboard at **http://localhost:3000**
Ask the agent to **open the dashboard** (`open_dashboard` tool), or visit that URL yourself.
---
## Workflow
| 1. Ingest context | You or agent | Paste meeting notes, specs, feedback as **documents** |
| 2. Extract | Agent (MCP) | Creates **extraction candidates** — decisions, assumptions, actions, evidence |
| 3. Review | You (dashboard) | Accept or reject candidates → **canonical graph** |
| 4. Detect drift | Agent (MCP) | Compares new context to existing decisions → **drift signals** |
| 5. Respond | You + agent | Acknowledge drift, edit **memo** drafts, mark memos final |
**Key rule:** the agent never writes canonical truth directly. It creates candidates, drift signals, and memo drafts. You confirm what becomes real.
### Linking the decision graph
When extracting assumptions, actions, or evidence, include `relates_to_decision_id` in the candidate payload so accepting auto-links the entity to a decision. Or call `create_relation` / use the dashboard linker afterward.
### Document status
Documents move through `new` → `extracted` → `archived`. Agents can fetch only `new` documents to avoid re-mining context that was already processed.
---
## MCP tools
| `open_dashboard` | Open the review UI in your browser |
| `create_document` | Store product context |
| `get_document` | Fetch a document with full text |
| `list_recent_documents` | List documents (`status`: `new`, `extracted`, `archived`) |
| `create_extraction_candidate` | Propose a decision, assumption, action, evidence, etc. |
| `list_decisions` | List canonical decisions |
| `get_decision_context` | Decision + linked assumptions, actions, evidence, drift |
| `create_relation` | Link two entities in the graph |
| `create_drift_signal` | Flag when new context may challenge a decision |
| `list_open_drift_signals` | List unreviewed drift |
| `create_memo` | Save a memo (`draft` or `final`) |
| `update_memo` | Edit a memo or mark it final |
| `list_memos` / `get_memo` | Read existing memos for context |
Enum values are passed as strings (e.g. `candidate_type`: `decision`, `assumption`, `action`, `evidence`).
### Example agent prompts
**Extraction**
```
Read documents with status "new". For each item found, create extraction candidates.
Do not create canonical decisions directly. For assumptions/actions/evidence, set
relates_to_decision_id in the payload when you know which decision they support.
```
**Drift**
```
Read the latest document, list decisions, inspect their context, and create drift
signals where new evidence contradicts or challenges existing decisions.
```
**Memo**
```
Read relevant memos and decision context, then create or update a memo draft
addressing the acknowledged drift signal. Mark final when complete.
```
---
## Configuration
All settings are optional — defaults work for local development.
| `DATABASE_URL` | `postgres://cockpit:cockpit@localhost:5432/decision_cockpit` | Postgres connection |
| `API_BIND_ADDR` | `0.0.0.0:3000` | HTTP bind address |
| `COCKPIT_AUTO_DOCKER` | `true` | Start Postgres via Docker on launch |
| `COCKPIT_SERVE_DASHBOARD` | `true` | Host API + embedded UI |
| `COCKPIT_FRONTEND_DIST` | *(embedded)* | Dev override: serve UI from disk |
| `CORS_ALLOWED_ORIGINS` | `http://localhost:5173` | CORS for Vite dev mode |
Copy `.env.example` to `.env` when developing from source.
---
## HTTP API
The dashboard and `api` binary share the same REST API at `http://localhost:3000`.
<details>
<summary>Endpoints</summary>
| GET | `/health` | Health check |
| POST / GET | `/documents` | Create / list documents |
| GET | `/documents/:id` | Get document |
| POST | `/documents/:id/status` | Set document status |
| GET | `/documents/:id/candidates` | Candidates for a document |
| GET | `/candidates` | List candidates |
| POST | `/candidates/:id/accept` | Accept into canonical graph |
| POST | `/candidates/:id/reject` | Reject candidate |
| GET | `/decisions` | List decisions |
| GET | `/decisions/:id` | Decision + linked context |
| GET | `/assumptions`, `/actions`, `/evidence` | Entity lists (for linking) |
| POST | `/relations` | Create a graph relation |
| GET | `/graph` | Full graph nodes + edges |
| GET | `/drift-signals` | List drift signals |
| POST | `/drift-signals/:id/accept` | Acknowledge drift |
| POST | `/drift-signals/:id/dismiss` | Dismiss drift |
| GET / POST | `/memos` | List / create memos |
| GET / PUT | `/memos/:id` | Get / update memo |
| POST | `/memos/from-drift/:id` | Create memo draft from drift |
</details>
```bash
curl -X POST http://localhost:3000/documents \
-H "content-type: application/json" \
-d '{"title":"Sprint notes","source_type":"meeting_note","raw_text":"We agreed to ship MCP-first distribution."}'
```
---
## Build from source
For contributors or pre-release builds:
```bash
git clone https://github.com/Pulko/layer
cd layer
# Dashboard must be built before cargo build (embedded at compile time)
cd frontend && npm install && npm run build && cd ..
cargo build --release --bin mcp
```
Other binaries:
```bash
cargo run --bin api # HTTP API + dashboard only
cargo run --bin mcp # MCP + dashboard (stdio)
```
**Dev tips**
- Frontend hot-reload: `cd frontend && npm run dev` with `COCKPIT_FRONTEND_DIST=./frontend/dist`
- Skip Docker bootstrap: `COCKPIT_AUTO_DOCKER=false` (bring your own Postgres)
---
## Architecture
```
src/
assets.rs # Dashboard embedded via rust-embed
bootstrap.rs # Docker Compose + DB readiness
domain/ # Entities and enums
services/ # Business logic (shared by HTTP + MCP)
http/ # Axum REST API
mcp/ # MCP tool server (rmcp)
bin/mcp.rs # Primary entrypoint
bin/api.rs # Standalone HTTP server
migrations/ # SQLx migrations
frontend/ # React dashboard (built into binary)
```
Both `mcp` and `api` link the same library crate. MCP is the intended runtime for agent-driven workflows.
---
## Security
Layer is designed for **local, single-user** use:
- Dashboard URL is `http://localhost:3000`; API binds to `0.0.0.0:3000` by default (set `API_BIND_ADDR` to restrict)
- Uses default Postgres credentials (`cockpit` / `cockpit`) suitable for local Docker only
- No authentication layer
Do not expose the HTTP port or database to untrusted networks without hardening.
---
## License
MIT — see [LICENSE](LICENSE).