# Intake Guide - Notes Fast-Capture System
A guide to using the notes intake system for rapid idea capture and triage.
See [MCP Tool Reference](../05-mcp-tool-reference.md) for the full tool/action list.
For the end-to-end flow, read the [Workflow Guide](../03-workflow-guide.md).
## Table of Contents
- [Overview](#overview)
- [Quick Start](#quick-start)
- [Actions](#actions)
- [intake-add](#intake-add)
- [intake-list](#intake-list)
- [intake-dismiss](#intake-dismiss)
- [Pagination](#pagination)
- [Storage and File Rotation](#storage-and-file-rotation)
- [Validation Constraints](#validation-constraints)
- [Common Workflows](#common-workflows)
- [Feature Flag](#feature-flag)
---
## Overview
Notes are a fast-capture queue for ideas, tasks, and requests that need quick recording without immediate triage. Think of them as a lightweight scratchpad for items you can triage later.
### Key Concepts
- **Intake Items**: Quick-capture records with title, description, priority, and tags
- **FIFO Queue**: Items are processed in first-in, first-out order
- **Two States**: Items are either `new` (active) or `dismissed` (processed/rejected)
- **Append-Only Storage**: Items are stored in a JSONL file with atomic writes
### Storage Location
Intake items are stored in:
```
specs/.notes/
intake.jsonl - Active intake items
.intake.lock - Lock file for synchronization
intake.YYYY-MM.jsonl - Archived files (after rotation)
```
---
## Quick Start
### Add an Item
```json
{
"action": "intake-add",
"title": "Add dark mode support",
"description": "Users have requested a dark theme option",
"priority": "p2",
"tags": ["feature", "ui"]
}
```
### List New Items
```json
{
"action": "intake-list",
"limit": 10
}
```
### Dismiss an Item
```json
{
"action": "intake-dismiss",
"intake_id": "intake-12345678-1234-1234-1234-123456789012",
"reason": "Duplicate of existing issue"
}
```
---
## Actions
### intake-add
Add a new item to the intake queue.
#### Parameters
| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `title` | string | Yes | - | Brief title (1-140 characters) |
| `description` | string | No | null | Detailed description (max 2000 characters) |
| `priority` | string | No | "p2" | Priority level: p0 (highest) to p4 (lowest) |
| `tags` | array | No | [] | List of tags (max 20, each 1-32 chars, lowercase) |
| `source` | string | No | null | Origin of the item (max 100 characters) |
| `requester` | string | No | null | Who requested the work (max 100 characters) |
| `idempotency_key` | string | No | null | Key for deduplication (max 64 characters) |
| `dry_run` | boolean | No | false | Validate without persisting |
#### Example Request
```json
{
"action": "intake-add",
"title": "Implement JWT authentication",
"description": "Replace session-based auth with JWT tokens for the API",
"priority": "p1",
"tags": ["security", "api", "auth"],
"source": "security-review",
"requester": "security-team@example.com",
"idempotency_key": "auth-migration-2024"
}
```
#### Example Response
```json
{
"success": true,
"data": {
"item": {
"schema_version": "intake-v1",
"id": "intake-a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"title": "Implement JWT authentication",
"description": "Replace session-based auth with JWT tokens for the API",
"status": "new",
"priority": "p1",
"tags": ["security", "api", "auth"],
"source": "security-review",
"requester": "security-team@example.com",
"idempotency_key": "auth-migration-2024",
"created_at": "2024-01-15T10:30:00.000Z",
"updated_at": "2024-01-15T10:30:00.000Z"
},
"was_duplicate": false,
"intake_path": "/workspace/specs/.notes/intake.jsonl"
}
}
```
#### Idempotency
When an `idempotency_key` is provided:
- If a matching key exists in the last 100 items, the existing item is returned
- `was_duplicate: true` indicates the item already existed
- Use idempotency keys to prevent duplicate submissions from retries
---
### intake-list
List intake items with status `new` in FIFO order (oldest first).
#### Parameters
| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `cursor` | string | No | null | Pagination cursor from previous response |
| `limit` | integer | No | 50 | Items per page (1-200) |
#### Example Request
```json
{
"action": "intake-list",
"limit": 10
}
```
#### Example Response
```json
{
"success": true,
"data": {
"items": [
{
"id": "intake-11111111-1111-1111-1111-111111111111",
"title": "First item",
"status": "new",
"priority": "p2",
"created_at": "2024-01-15T10:00:00.000Z"
},
{
"id": "intake-22222222-2222-2222-2222-222222222222",
"title": "Second item",
"status": "new",
"priority": "p1",
"created_at": "2024-01-15T11:00:00.000Z"
}
],
"total_count": 25,
"has_more": true,
"next_cursor": "eyJsYXN0X2lkIjoiaW50YWtlLTIyMjIyMjIy..."
}
}
```
#### Notes
- Only items with `status: "new"` are returned
- Items are ordered by creation time (FIFO)
- Use the `next_cursor` value to fetch the next page
---
### intake-dismiss
Dismiss an intake item by changing its status to `dismissed`.
#### Parameters
| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `intake_id` | string | Yes | - | The intake item ID to dismiss |
| `reason` | string | No | null | Reason for dismissal (max 200 characters) |
| `dry_run` | boolean | No | false | Find item but don't modify |
#### Example Request
```json
{
"action": "intake-dismiss",
"intake_id": "intake-a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"reason": "Converted to spec: feature-auth-2024-001"
}
```
#### Example Response
```json
{
"success": true,
"data": {
"item": {
"id": "intake-a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"title": "Implement JWT authentication",
"status": "dismissed",
"updated_at": "2024-01-16T09:00:00.000Z"
}
}
}
```
#### Notes
- Dismissed items remain in the file but are excluded from `intake-list`
- Use the `reason` field to document why the item was dismissed
- Common reasons: "Converted to spec", "Duplicate", "Won't fix", "Out of scope"
---
## Pagination
The intake list uses cursor-based pagination for efficient traversal of large datasets.
### How It Works
1. **Initial Request**: Call `intake-list` without a cursor
2. **Check for More**: If `has_more: true`, save the `next_cursor` value
3. **Continue**: Call `intake-list` with `cursor: <next_cursor>` to get the next page
4. **Complete**: When `has_more: false`, you've reached the end
### Example: Paginating Through All Items
```python
cursor = None
all_items = []
while True:
response = call_intake_list(cursor=cursor, limit=50)
all_items.extend(response["data"]["items"])
if not response["data"]["has_more"]:
break
cursor = response["data"]["next_cursor"]
print(f"Total items: {len(all_items)}")
```
### Cursor Internals
Cursors are base64-encoded JSON containing:
- `last_id`: ID of the last item on the previous page
- `line_hint`: File line position hint for efficient seeking
- `version`: Cursor format version for forward compatibility
Cursors may become stale if the file is rotated. The system handles this gracefully by falling back to a full scan.
---
## Storage and File Rotation
### JSONL Format
Items are stored as JSON Lines (one JSON object per line):
```jsonl
{"schema_version":"intake-v1","id":"intake-...","title":"First item",...}
{"schema_version":"intake-v1","id":"intake-...","title":"Second item",...}
```
### File Rotation
To prevent unbounded file growth, the intake file is rotated when:
- Item count exceeds **1000 items**, OR
- File size exceeds **1 MB**
When rotation occurs:
1. The current file is renamed to `intake.YYYY-MM.jsonl` (based on oldest item date)
2. A new empty `intake.jsonl` is created
3. If an archive with the same name exists, a numeric suffix is added
### Concurrency Safety
- File operations use `fcntl` locking for cross-process safety
- Thread-level locking ensures thread safety within a process
- Lock acquisition times out after 5 seconds to prevent deadlocks
---
## Validation Constraints
### Field Constraints
| Field | Constraint |
|-------|------------|
| `title` | Required, 1-140 characters |
| `description` | Optional, max 2000 characters |
| `priority` | One of: p0, p1, p2, p3, p4 |
| `tags` | Max 20 tags, each 1-32 chars, pattern: `[a-z0-9_-]+` |
| `source` | Optional, max 100 characters |
| `requester` | Optional, max 100 characters |
| `idempotency_key` | Optional, max 64 characters |
| `reason` (dismiss) | Optional, max 200 characters |
### Input Sanitization
All string inputs are sanitized:
- Control characters (0x00-0x1F) are stripped, except `\n`, `\r`, `\t`
- Prompt injection patterns are detected and sanitized
- Tags are normalized to lowercase
### ID Format
Intake IDs follow the pattern: `intake-<uuid4>`
Example: `intake-a1b2c3d4-e5f6-7890-abcd-ef1234567890`
---
## Common Workflows
### Workflow 1: Quick Capture During Meeting
```json
// Rapidly capture action items
{"action": "intake-add", "title": "Review API rate limits", "priority": "p2"}
{"action": "intake-add", "title": "Update onboarding docs", "priority": "p3"}
{"action": "intake-add", "title": "Fix login timeout bug", "priority": "p1"}
```
### Workflow 2: Triage Session
```python
# 1. List all new items
items = intake_list(limit=100)
# 2. For each item, decide:
for item in items:
if should_create_spec(item):
# Create a spec from the item
create_spec_from_intake(item)
intake_dismiss(item["id"], reason="Converted to spec")
elif is_duplicate(item):
intake_dismiss(item["id"], reason="Duplicate of existing issue")
elif out_of_scope(item):
intake_dismiss(item["id"], reason="Out of scope for current cycle")
# else: leave as new for further discussion
```
### Workflow 3: Deduplication with Idempotency Keys
```json
// First submission
{
"action": "intake-add",
"title": "Add search feature",
"idempotency_key": "search-feature-2024"
}
// Response: was_duplicate: false
// Retry (network hiccup, etc.)
{
"action": "intake-add",
"title": "Add search feature",
"idempotency_key": "search-feature-2024"
}
// Response: was_duplicate: true, returns original item
```
---
## Best Practices
1. **Keep titles concise**: Use the description for details
2. **Use consistent tags**: Establish a tag taxonomy for your team
3. **Set meaningful priorities**: Reserve p0 for true emergencies
4. **Use idempotency keys**: Prevent duplicates from retries
5. **Triage regularly**: Don't let the intake queue grow too large
6. **Document dismissal reasons**: Future reference for why items were dropped