Skip to content

Commit 9bed5b3

Browse files
authored
feat: Add new app concept and better query cli command options to limit context usage (#21)
2 parents aebe1cf + ca4aace commit 9bed5b3

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+1592
-172
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
---
2+
"agentcrumbs": minor
3+
---
4+
5+
Add app concept for project-level crumb isolation and cursor-based query pagination.
6+
7+
**App isolation:**
8+
- Every crumb is stamped with an `app` name, auto-detected from the nearest `package.json`
9+
- Crumbs stored per-app at `~/.agentcrumbs/<app>/crumbs.jsonl`
10+
- Collector routes incoming crumbs to per-app stores
11+
- All CLI commands scope to the current app by default
12+
- Override with `--app <name>`, `--all-apps`, `AGENTCRUMBS_APP` env var, or `app` field in JSON config
13+
14+
**Query pagination:**
15+
- New `--cursor` flag for forward pagination with short 8-char cursor IDs
16+
- New `--after` and `--before` flags for absolute ISO timestamp windows
17+
- Default limit reduced from 100 to 50 per page
18+
- Results returned oldest-first with `Next: --cursor <id>` in output when more pages exist
19+
20+
**New files:**
21+
- `src/cli/app-store.ts` — shared helper for app context resolution across CLI commands
22+
- `src/cli/cursor.ts` — cursor storage with 1-hour TTL
23+
24+
**Breaking changes:**
25+
- `Crumb` type now has a required `app: string` field
26+
- `AgentCrumbsConfig` type now has an optional `app?: string` field
27+
- `CollectorServer` no longer exposes `getStore()` (routes to per-app stores internally)
28+
- Storage location changed from `~/.agentcrumbs/crumbs.jsonl` to `~/.agentcrumbs/<app>/crumbs.jsonl`
29+
- Legacy flat-file crumbs (without `app` field) are still readable as app `"unknown"`

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
node_modules
22
dist
3+
.tshy
34
*.tsbuildinfo
45
.DS_Store
56
.env

README.md

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Crumbs are development-only. They get stripped before merge and cost nothing whe
77
```
88
Service A ──┐ ┌── $ agentcrumbs tail
99
Service B ──┤── fetch() ──> Collector :8374 ──┤── $ agentcrumbs query --since 5m
10-
Service C ──┘ (fire & forget) └── ~/.agentcrumbs/crumbs.jsonl
10+
Service C ──┘ (fire & forget) └── ~/.agentcrumbs/<app>/crumbs.jsonl
1111
```
1212

1313
## Getting started
@@ -54,14 +54,16 @@ When something goes wrong, the agent starts the collector and queries the trail:
5454
```bash
5555
agentcrumbs collect --quiet &
5656
AGENTCRUMBS=1 node app.js
57-
agentcrumbs query --since 5m --ns auth-service
57+
agentcrumbs query --since 5m
5858
```
5959

6060
```
6161
auth-service login attempt +0ms { tokenPrefix: "eyJhbGci" }
6262
auth-service token decode ok +3ms { userId: "u_8f3k" }
6363
auth-service permissions check +8ms { roles: [] }
6464
auth-service rejected: no roles +8ms { status: 401 }
65+
66+
4 crumbs.
6567
```
6668

6769
Now the agent knows: the token is valid, but the user has no roles. The fix is in role assignment, not token validation.
@@ -114,8 +116,11 @@ Everything is controlled by a single `AGENTCRUMBS` environment variable.
114116
| `auth-*,api-*` | Multiple patterns (comma or space separated) |
115117
| `* -internal-*` | Match all except excluded patterns |
116118
| `{"ns":"*","port":9999}` | JSON config with full control |
119+
| `{"app":"my-app","ns":"*"}` | Explicit app name |
120+
121+
JSON config fields: `app` (app name, default auto-detect from package.json), `ns` (namespace filter, required), `port` (collector port, default 8374), `format` (`"pretty"` or `"json"`, default `"pretty"`).
117122

118-
JSON config fields: `ns` (namespace filter, required), `port` (collector port, default 8374), `format` (`"pretty"` or `"json"`, default `"pretty"`).
123+
You can also set `AGENTCRUMBS_APP` to override the app name independently.
119124

120125
## CLI
121126

@@ -127,24 +132,27 @@ agentcrumbs collect --quiet & # Start in background
127132
agentcrumbs collect --port 9999 # Custom port
128133

129134
# Live tail
130-
agentcrumbs tail # All namespaces
135+
agentcrumbs tail # All namespaces (scoped to current app)
131136
agentcrumbs tail --ns auth-service # Filter by namespace
132-
agentcrumbs tail --tag perf # Filter by tag
137+
agentcrumbs tail --app my-app # Tail a specific app
138+
agentcrumbs tail --all-apps # Tail all apps
133139

134-
# Query
135-
agentcrumbs query --since 5m # Last 5 minutes
136-
agentcrumbs query --ns auth-service --since 1h
137-
agentcrumbs query --tag root-cause
138-
agentcrumbs query --json --limit 50
140+
# Query (paginated, 50 per page)
141+
agentcrumbs query --since 5m # Last 5 minutes
142+
agentcrumbs query --since 5m --cursor a1b2c3d4 # Next page
143+
agentcrumbs query --since 1h --limit 25 # Smaller pages
144+
agentcrumbs query --session a1b2c3 # Filter by session
145+
agentcrumbs query --tag root-cause # Filter by tag
139146

140147
# Strip
141148
agentcrumbs strip --dry-run # Preview removals
142149
agentcrumbs strip # Remove all crumb code
143150
agentcrumbs strip --check # CI gate (exits 1 if markers found)
144151

145152
# Utilities
146-
agentcrumbs stats # Crumb counts, file size
147-
agentcrumbs clear # Delete stored crumbs
153+
agentcrumbs stats # Crumb counts (current app)
154+
agentcrumbs stats --all-apps # Stats for all apps
155+
agentcrumbs clear # Clear crumbs (current app)
148156
```
149157

150158
Time units: `s` (seconds), `m` (minutes), `h` (hours), `d` (days).
@@ -160,7 +168,7 @@ The collector is language-agnostic. Any language with HTTP support can send crum
160168
```bash
161169
curl -X POST http://localhost:8374/crumb \
162170
-H "Content-Type: application/json" \
163-
-d '{"ts":"2026-01-01T00:00:00Z","ns":"shell","msg":"hello","type":"crumb","dt":0,"pid":1}'
171+
-d '{"app":"my-app","ts":"2026-01-01T00:00:00Z","ns":"shell","msg":"hello","type":"crumb","dt":0,"pid":1}'
164172
```
165173

166174
## Runtime compatibility

docs/content/docs/cli/collect.mdx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ The collector is an HTTP server that receives crumbs via `POST /crumb` and write
1111
agentcrumbs collect
1212
# agentcrumbs collector
1313
# http: http://localhost:8374/crumb
14-
# store: ~/.agentcrumbs/crumbs.jsonl
14+
# crumbs stored per-app in ~/.agentcrumbs/<app>/
1515
# press ctrl+c to stop
1616
```
1717

@@ -50,8 +50,8 @@ agentcrumbs collect --quiet
5050

5151
1. Listens on the specified port for `POST /crumb` requests
5252
2. Validates the JSON body matches the crumb schema
53-
3. Appends each crumb as a line to `crumbs.jsonl`
54-
4. The `tail` and `query` commands read from this file
53+
3. Routes each crumb to per-app storage at `~/.agentcrumbs/<app>/crumbs.jsonl`
54+
4. The `tail` and `query` commands read from these files
5555

5656
### Without the collector
5757

docs/content/docs/cli/other.mdx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,25 @@ title: "Other commands"
33
description: "stats, clear, follow, replay, sessions"
44
---
55

6+
All commands below accept `--app <name>` to scope to a specific app and `--all-apps` to include all apps. Default is auto-detect from `package.json`.
7+
68
## `agentcrumbs stats`
79

810
Show crumb counts, file size, and active services.
911

1012
```bash
11-
agentcrumbs stats
13+
agentcrumbs stats # current app
14+
agentcrumbs stats --all-apps # per-app breakdown
1215
```
1316

1417
## `agentcrumbs clear`
1518

16-
Delete all stored crumbs.
19+
Delete stored crumbs.
1720

1821
```bash
19-
agentcrumbs clear
22+
agentcrumbs clear # clear current app
23+
agentcrumbs clear --all-apps # clear all apps
24+
agentcrumbs clear --app foo # clear a specific app
2025
```
2126

2227
## `agentcrumbs sessions`

docs/content/docs/cli/query.mdx

Lines changed: 39 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ description: "Search historical crumbs"
55

66
## `agentcrumbs query`
77

8-
Query historical crumbs with time ranges and filters.
8+
Query historical crumbs with time windows and cursor-based pagination.
99

1010
```bash
1111
agentcrumbs query --since 5m
@@ -15,34 +15,61 @@ agentcrumbs query --since 5m
1515

1616
| Flag | Description |
1717
| --- | --- |
18-
| `--since <duration>` | Time range (e.g., `5m`, `1h`, `24h`, `7d`) |
18+
| `--since <duration>` | Relative time window (e.g., `5m`, `1h`, `24h`, `7d`) |
19+
| `--after <timestamp>` | Crumbs after this ISO timestamp |
20+
| `--before <timestamp>` | Crumbs before this ISO timestamp |
21+
| `--cursor <id>` | Resume from a previous page (8-char ID from output) |
22+
| `--limit <n>` | Results per page (default: 50) |
1923
| `--ns <pattern>` | Filter by namespace |
2024
| `--tag <tag>` | Filter by tag |
2125
| `--session <id>` | Filter by session ID |
2226
| `--match <text>` | Text search |
27+
| `--app <name>` | Scope to a specific app (default: auto-detect from package.json) |
28+
| `--all-apps` | Query crumbs from all apps |
2329
| `--json` | JSON output |
24-
| `--limit <n>` | Maximum number of results |
2530

2631
Time units: `s` (seconds), `m` (minutes), `h` (hours), `d` (days).
2732

33+
### Pagination
34+
35+
Results are returned oldest-first, capped at `--limit` (default 50). When there are more results, the output includes a short cursor ID for the next page.
36+
37+
```bash
38+
# First page
39+
agentcrumbs query --since 5m
40+
# Output: 50 crumbs (1-50 of 128). Next: --cursor a1b2c3d4
41+
42+
# Next page
43+
agentcrumbs query --since 5m --cursor a1b2c3d4
44+
# Output: 50 crumbs (51-100 of 128). Next: --cursor e5f6g7h8
45+
```
46+
47+
Cursors expire after 1 hour. You can also use `--after` / `--before` with ISO timestamps for explicit time windows without cursors.
48+
2849
### Examples
2950

3051
```bash
31-
# Last 5 minutes
52+
# Last 5 minutes (all namespaces)
3253
agentcrumbs query --since 5m
3354

34-
# Last hour, filtered by namespace
35-
agentcrumbs query --since 1h --ns auth-service
55+
# Paginate through results
56+
agentcrumbs query --since 5m --cursor a1b2c3d4
3657

37-
# Filter by tag
38-
agentcrumbs query --tag root-cause
58+
# Time window with absolute timestamps
59+
agentcrumbs query --after 2026-03-11T14:00:00Z --before 2026-03-11T14:05:00Z
60+
61+
# Smaller pages
62+
agentcrumbs query --since 1h --limit 25
3963

4064
# Filter by session
4165
agentcrumbs query --session a1b2c3
4266

43-
# JSON output with limit
44-
agentcrumbs query --since 1h --json --limit 50
67+
# Filter by tag
68+
agentcrumbs query --tag root-cause
69+
70+
# Query a specific app
71+
agentcrumbs query --since 1h --app my-project
4572

46-
# Text search
47-
agentcrumbs query --since 24h --match "connection refused"
73+
# Query across all apps
74+
agentcrumbs query --since 5m --all-apps
4875
```

docs/content/docs/cli/tail.mdx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ agentcrumbs tail
1919
| `--tag <tag>` | Filter by tag |
2020
| `--match <text>` | Filter by content |
2121
| `--session <id>` | Filter by session ID |
22+
| `--app <name>` | Scope to a specific app (default: auto-detect from package.json) |
23+
| `--all-apps` | Show crumbs from all apps |
2224
| `--json` | JSON output (for piping to jq, etc.) |
2325

2426
### Examples
@@ -37,6 +39,12 @@ agentcrumbs tail --match "userId:123"
3739
# Filter by session
3840
agentcrumbs tail --session a1b2c3
3941

42+
# Scope to a specific app
43+
agentcrumbs tail --app my-project
44+
45+
# Show crumbs from all apps
46+
agentcrumbs tail --all-apps
47+
4048
# JSON output for piping
4149
agentcrumbs tail --json | jq '.data.userId'
4250
```

docs/content/docs/config/env-var.mdx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,16 +43,35 @@ AGENTCRUMBS='{"ns":"*","port":9999}'
4343

4444
# JSON output format (instead of pretty)
4545
AGENTCRUMBS='{"ns":"*","format":"json"}'
46+
47+
# Explicit app name
48+
AGENTCRUMBS='{"app":"my-project","ns":"*"}'
4649
```
4750

4851
## Config schema
4952

5053
| Field | Type | Default | Description |
5154
| --- | --- | --- | --- |
55+
| `app` | `string` | (auto-detect) | App name. Defaults to nearest `package.json` name |
5256
| `ns` | `string` | (required) | Namespace filter pattern |
5357
| `port` | `number` | `8374` | Collector HTTP port |
5458
| `format` | `"pretty"` \| `"json"` | `"pretty"` | Output format for stderr |
5559

60+
## App name
61+
62+
Every crumb is stamped with an app name. This keeps crumbs from different projects separate.
63+
64+
The app name is resolved in this order:
65+
1. `app` field in the JSON config
66+
2. `AGENTCRUMBS_APP` environment variable
67+
3. Auto-detected from the nearest `package.json` name field (walking up from `cwd`)
68+
4. Fallback: `"unknown"`
69+
70+
```bash
71+
# Override via dedicated env var
72+
AGENTCRUMBS_APP=my-project AGENTCRUMBS=1 node app.js
73+
```
74+
5675
## Namespace patterns
5776

5877
- `*` matches everything

docs/content/docs/crumb-format.mdx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Each crumb is a JSON object. When stored, they are written as JSONL (one JSON ob
99

1010
```json
1111
{
12+
"app": "my-project",
1213
"ts": "2026-03-07T10:00:00.123Z",
1314
"ns": "auth-service",
1415
"msg": "user logged in",
@@ -28,6 +29,7 @@ Each crumb is a JSON object. When stored, they are written as JSONL (one JSON ob
2829

2930
| Field | Type | Required | Description |
3031
| --- | --- | --- | --- |
32+
| `app` | `string` | Yes | App name (auto-detected from `package.json` or explicit config) |
3133
| `ts` | `string` | Yes | ISO 8601 timestamp |
3234
| `ns` | `string` | Yes | Namespace |
3335
| `msg` | `string` | Yes | Message |
@@ -57,4 +59,4 @@ Each crumb is a JSON object. When stored, they are written as JSONL (one JSON ob
5759

5860
## Storage
5961

60-
Crumbs are stored in `~/.agentcrumbs/crumbs.jsonl` by default. One JSON object per line, no trailing comma, no wrapping array.
62+
Crumbs are stored per-app at `~/.agentcrumbs/<app>/crumbs.jsonl`. One JSON object per line, no trailing comma, no wrapping array. The app name is auto-detected from the nearest `package.json` by default.

docs/content/docs/guides/cross-language.mdx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Send a `POST` request to `http://localhost:8374/crumb` with a JSON body matching
1313

1414
| Field | Type | Description |
1515
| --- | --- | --- |
16+
| `app` | `string` | App name (used for per-app storage routing) |
1617
| `ts` | `string` | ISO 8601 timestamp |
1718
| `ns` | `string` | Namespace |
1819
| `msg` | `string` | Message |
@@ -38,7 +39,7 @@ Send a `POST` request to `http://localhost:8374/crumb` with a JSON body matching
3839
```bash
3940
curl -X POST http://localhost:8374/crumb \
4041
-H "Content-Type: application/json" \
41-
-d '{"ts":"2026-01-01T00:00:00Z","ns":"shell","msg":"hello","type":"crumb","dt":0,"pid":1}'
42+
-d '{"app":"my-app","ts":"2026-01-01T00:00:00Z","ns":"shell","msg":"hello","type":"crumb","dt":0,"pid":1}'
4243
```
4344

4445
### Python
@@ -50,6 +51,7 @@ from datetime import datetime
5051
def crumb(ns, msg, data=None):
5152
try:
5253
requests.post("http://localhost:8374/crumb", json={
54+
"app": "my-app",
5355
"ts": datetime.utcnow().isoformat() + "Z",
5456
"ns": ns,
5557
"msg": msg,
@@ -69,6 +71,7 @@ crumb("python-service", "processing started", {"items": 42})
6971
```go
7072
func crumb(ns, msg string, data any) {
7173
body, _ := json.Marshal(map[string]any{
74+
"app": "my-app",
7275
"ts": time.Now().UTC().Format(time.RFC3339Nano),
7376
"ns": ns,
7477
"msg": msg,
@@ -86,6 +89,7 @@ func crumb(ns, msg string, data any) {
8689
```rust
8790
fn crumb(ns: &str, msg: &str) {
8891
let body = serde_json::json!({
92+
"app": "my-app",
8993
"ts": chrono::Utc::now().to_rfc3339(),
9094
"ns": ns,
9195
"msg": msg,

0 commit comments

Comments
 (0)