@aitne-sh/aitne 0.1.4 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -0
- package/agent-assets/agent-profiles/_safety.md +29 -0
- package/agent-assets/agent-profiles/routine-fetch-window.md +75 -40
- package/agent-assets/agent-profiles/wiki-agent.md +19 -0
- package/agent-assets/docs/features/messaging/bang-commands.md +161 -0
- package/agent-assets/docs/features/messaging/overview.md +3 -0
- package/agent-assets/docs/features/wiki/commands.md +222 -0
- package/agent-assets/docs/features/wiki/overview.md +145 -0
- package/agent-assets/docs/getting-started/03-what-can-this-do.md +18 -0
- package/agent-assets/docs/glossary.md +34 -0
- package/agent-assets/docs/guides/budget-and-cost-for-wiki.md +123 -0
- package/agent-assets/docs/guides/build-your-wiki.md +99 -0
- package/agent-assets/docs/guides/explore-with-trace-and-connect.md +169 -0
- package/agent-assets/docs/guides/maintain-wiki-health.md +168 -0
- package/agent-assets/docs/guides/multiple-wikis-for-multiple-domains.md +192 -0
- package/agent-assets/docs/guides/pause-the-agent.md +10 -3
- package/agent-assets/docs/guides/use-an-existing-obsidian-vault.md +156 -0
- package/agent-assets/docs/reference/cli-commands.md +24 -1
- package/agent-assets/docs/troubleshooting/wiki-ingest-full-blocked.md +96 -0
- package/agent-assets/docs/troubleshooting/wiki-write-failed.md +82 -0
- package/agent-assets/skills/context/SKILL.md +288 -17
- package/agent-assets/skills/external-services/SKILL.delegated.claude.md +2 -2
- package/agent-assets/skills/external-services/SKILL.delegated.codex.md +3 -3
- package/agent-assets/skills/external-services/SKILL.delegated.gemini.md +6 -6
- package/agent-assets/skills/external-services/SKILL.md +5 -3
- package/agent-assets/skills/external-services/SKILL.native.claude.md +49 -58
- package/agent-assets/skills/external-services/SKILL.native.codex.md +50 -58
- package/agent-assets/skills/external-services/SKILL.native.gemini.md +53 -56
- package/agent-assets/skills/mail/SKILL.md +5 -5
- package/agent-assets/skills/mail/SKILL.native.claude.md +57 -65
- package/agent-assets/skills/mail/SKILL.native.codex.md +73 -75
- package/agent-assets/skills/mail/SKILL.native.gemini.md +80 -75
- package/agent-assets/skills/management-task-register/SKILL.md +3 -3
- package/agent-assets/skills/notion/SKILL.native.claude.md +78 -82
- package/agent-assets/skills/notion/SKILL.native.codex.md +78 -80
- package/agent-assets/skills/notion/SKILL.native.gemini.md +91 -90
- package/agent-assets/skills/observations/SKILL.md +123 -15
- package/agent-assets/skills/roadmap/SKILL.md +31 -4
- package/agent-assets/skills/schedule/SKILL.md +44 -3
- package/agent-assets/skills/today/SKILL.md +50 -11
- package/agent-assets/skills/travel-time/SKILL.md +9 -0
- package/agent-assets/skills/wiki/wiki-ask/SKILL.md +32 -0
- package/agent-assets/skills/wiki/wiki-compile/SKILL.md +126 -0
- package/agent-assets/skills/wiki/wiki-connect/SKILL.md +75 -0
- package/agent-assets/skills/wiki/wiki-graduate/SKILL.md +45 -0
- package/agent-assets/skills/wiki/wiki-ingest/SKILL.md +182 -0
- package/agent-assets/skills/wiki/wiki-lint/SKILL.md +90 -0
- package/agent-assets/skills/wiki/wiki-trace/SKILL.md +72 -0
- package/agent-assets/skills/wiki/wiki-vault-rules/SKILL.md +145 -0
- package/agent-assets/task-flows/_partials/calendar-acquire.google_calendar.md +28 -9
- package/agent-assets/task-flows/_partials/calendar-acquire.outlook_calendar.md +26 -9
- package/agent-assets/task-flows/_partials/mail-acquire.gmail.md +51 -24
- package/agent-assets/task-flows/_partials/mail-acquire.outlook_mail.md +46 -16
- package/agent-assets/task-flows/_partials/notion-acquire.notion.md +29 -9
- package/agent-assets/task-flows/message.received.dm.md +35 -2
- package/agent-assets/task-flows/message.received.dm.native.claude.md +25 -26
- package/agent-assets/task-flows/message.received.dm.native.codex.md +30 -24
- package/agent-assets/task-flows/message.received.dm.native.gemini.md +36 -36
- package/agent-assets/task-flows/message.received.dm_first.md +43 -4
- package/agent-assets/task-flows/message.received.dm_first.native.claude.md +20 -20
- package/agent-assets/task-flows/message.received.dm_first.native.codex.md +22 -19
- package/agent-assets/task-flows/message.received.dm_first.native.gemini.md +28 -24
- package/agent-assets/task-flows/routine.fetch_window.md +51 -36
- package/agent-assets/task-flows/routine.morning_routine.md +12 -3
- package/agent-assets/task-flows/routine.morning_routine_initial.md +22 -1
- package/agent-assets/task-flows/routine.roadmap_refresh.md +7 -3
- package/agent-assets/task-flows/scheduled.dm.md +477 -0
- package/agent-assets/task-flows/setup.initial.md +50 -23
- package/agent-assets/task-flows/wiki.ask.md +11 -0
- package/agent-assets/task-flows/wiki.compile.md +28 -0
- package/agent-assets/task-flows/wiki.connect.md +12 -0
- package/agent-assets/task-flows/wiki.ingest_url.md +35 -0
- package/agent-assets/task-flows/wiki.lint.md +13 -0
- package/agent-assets/task-flows/wiki.trace.md +13 -0
- package/agent-assets/wiki-seeds/schemas/output.md +12 -0
- package/agent-assets/wiki-seeds/schemas/raw.md +13 -0
- package/agent-assets/wiki-seeds/schemas/wiki.md +12 -0
- package/agent-assets/wiki-seeds/taxonomy.md +13 -0
- package/package.json +10 -6
- package/scripts/check-redaction-coverage.mjs +0 -109
- package/scripts/commands.md +0 -0
- package/scripts/message-discipline-digest.mjs +0 -535
- package/scripts/poc/google-connector-inheritance/REPORT.md +0 -197
- package/scripts/poc/google-connector-inheritance/claude-sdk-probe.mjs +0 -79
- package/scripts/regen-skill-fixtures.mjs +0 -39
- package/scripts/remint-roadmap-ids.mjs +0 -257
- package/scripts/smoke-obsidian-api.mjs +0 -166
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: wiki-vault-rules
|
|
3
|
+
description: Load for every wiki.* process. Defines wiki vault layout, layer ownership, and the daemon Wiki API call convention (endpoints + canonical curl shape).
|
|
4
|
+
allowed-tools:
|
|
5
|
+
- Bash(curl *)
|
|
6
|
+
- Bash(jq *)
|
|
7
|
+
- Read
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Wiki Vault Rules
|
|
11
|
+
|
|
12
|
+
The wiki vault is `{{vault_path}}` for workspace `{{workspace_name}}` (schema `{{schema_version}}`, language `{{language}}`).
|
|
13
|
+
|
|
14
|
+
Never write this directory directly. Use only the daemon Wiki API at `http://localhost:8321`:
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
GET /api/wiki/{{workspace_name}}/index
|
|
18
|
+
GET /api/wiki/{{workspace_name}}/search?q=<query>
|
|
19
|
+
GET /api/wiki/{{workspace_name}}/files/<path>
|
|
20
|
+
POST /api/wiki/{{workspace_name}}/files/<path>
|
|
21
|
+
PATCH /api/wiki/{{workspace_name}}/files/<path>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Every request must include `x-process-key` with your current process key.
|
|
25
|
+
|
|
26
|
+
## Layers
|
|
27
|
+
|
|
28
|
+
- `00_inbox/` is human-only. Agents may read it but must never write it.
|
|
29
|
+
- `10_raw/` is create-only source capture. `wiki.ingest_url` may create root-level `<slug>.md`; existing raw notes are immutable.
|
|
30
|
+
- `10_raw/images/<slug>/<file>` is reserved for source images.
|
|
31
|
+
- `20_wiki/` is synthesized knowledge. `wiki.compile` may write root-level `<slug>.md` and `_index.md`.
|
|
32
|
+
- `30_outputs/` is answers and reports. `wiki.ask` may write root-level `<YYYY-MM-DD>-<slug>.md`.
|
|
33
|
+
- `90_meta/` holds taxonomy, schemas, and health notes. Treat schema files as read-mostly.
|
|
34
|
+
- `log.md` is append-only operational history (PATCH `mode: "append"` only).
|
|
35
|
+
|
|
36
|
+
Slugs are lowercase kebab-case (`^[a-z0-9][a-z0-9-]*$`). Preserve source URLs in raw and wiki notes.
|
|
37
|
+
|
|
38
|
+
## Calling the Wiki API from Bash
|
|
39
|
+
|
|
40
|
+
The session installs a node-backed curl shim at `.pa/bin/curl` (first on `PATH`) which pins host/port and auto-attaches read-side auth. From your point of view it behaves like plain `curl` against `http://localhost:8321/api/*` — with the constraints below.
|
|
41
|
+
|
|
42
|
+
### The command MUST start with the literal `curl` token
|
|
43
|
+
|
|
44
|
+
The session's allow-list is `Bash(curl *)`, **prefix-matched against the full command string**. These shapes are silently denied under `dontAsk` (no error, no `PA_API_ERROR`, no body — the call simply does not run):
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
echo '{...}' | curl ... # starts with `echo`
|
|
48
|
+
cat <<JSON | curl ... -d @- # starts with `cat`
|
|
49
|
+
bash -c "curl ..." # starts with `bash`
|
|
50
|
+
( curl ... ) # starts with `(`
|
|
51
|
+
curl1=... ; curl ... # starts with `curl1` then `;` — chained-curl deny
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Write a single, flat curl call. Multi-line / large bodies use a heredoc redirected directly to curl's stdin (the command still starts with `curl`):
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
curl http://... -X POST -H 'content-type: application/json' \
|
|
58
|
+
-H 'x-process-key: ...' -d @- <<'JSON'
|
|
59
|
+
{"content":"... multi-line body ..."}
|
|
60
|
+
JSON
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
The shim reads stdin when `-d @-` is passed, so the heredoc body lands as the request payload. For small single-line bodies, prefer `-d '<inline-json>'`.
|
|
64
|
+
|
|
65
|
+
### Allowed curl flags
|
|
66
|
+
|
|
67
|
+
The shim only understands `-X/--request`, `-H/--header`, `-d/--data/--data-raw/--data-binary`, `-o/--output`, `-F/--form`, and silently ignores `-s/--silent/--show-error`. Any other flag exits with `Unsupported curl flag`.
|
|
68
|
+
|
|
69
|
+
### Allowed headers
|
|
70
|
+
|
|
71
|
+
`content-type`, `x-process-key`, `x-lock-id`, `x-session-id`. Other authentication headers are managed by the shim — do not pass `Authorization`, `x-read-token`, or environment-variable expansions; they will be rejected.
|
|
72
|
+
|
|
73
|
+
### Body-quoting cheat sheet
|
|
74
|
+
|
|
75
|
+
For inline `-d '<json>'` bodies, wrap the JSON in **outer single quotes** so the shell does not expand it, then follow JSON's own escapes inside:
|
|
76
|
+
|
|
77
|
+
| Need in `content` | Write in the shell-arg |
|
|
78
|
+
|---|---|
|
|
79
|
+
| `"` | `\"` |
|
|
80
|
+
| `\` | `\\` |
|
|
81
|
+
| newline | `\n` |
|
|
82
|
+
| `'` | `'\''` (close-escape-reopen), or substitute `’` (U+2019) |
|
|
83
|
+
| `$`, backticks | leave as-is |
|
|
84
|
+
|
|
85
|
+
For heredoc `-d @-` bodies the shell does NOT expand the heredoc body when the marker is single-quoted (`<<'JSON'`), so the JSON inside is verbatim — no shell escaping required, only JSON's own (`\"`, `\\`, `\n`). Use heredoc for any body that's more than a few lines or contains lots of `"`.
|
|
86
|
+
|
|
87
|
+
`-d @<path>` (file-read) is rejected by both the security hook and the shim — there is no agent-facing reason to load a body from disk.
|
|
88
|
+
|
|
89
|
+
### What ELSE is silently denied (no `Bash(find|ls|cat|...)`)
|
|
90
|
+
|
|
91
|
+
Only `Bash(curl *)` and `Bash(jq *)` are on the allow-list. `Bash(find ...)`, `Bash(ls ...)`, `Bash(cat ...)`, `Bash(grep ...)`, `Bash(wc ...)`, and every other shell utility hit the same `dontAsk` denial — no useful tool result. Enumerate the workspace via `GET /api/wiki/<ws>/index` (returns `{ files: [{ path, mtime, sizeBytes }] }`); do not walk `{{vault_path}}` from disk.
|
|
92
|
+
|
|
93
|
+
`Write` and `Edit` tools are **stripped from the session allow-list** for every `wiki.*` process key — the SDK denies them silently. The Wiki API is the only legal write surface; `{{vault_path}}` is informational, not a target.
|
|
94
|
+
|
|
95
|
+
### Canonical shapes
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
# LIST every file in the workspace (returns JSON with path/mtime/sizeBytes per file)
|
|
99
|
+
curl http://localhost:8321/api/wiki/{{workspace_name}}/index \
|
|
100
|
+
-H 'x-process-key: <your-process-key>'
|
|
101
|
+
|
|
102
|
+
# SEARCH bodies for a substring (returns matches with snippet + mtime)
|
|
103
|
+
curl 'http://localhost:8321/api/wiki/{{workspace_name}}/search?q=<query>' \
|
|
104
|
+
-H 'x-process-key: <your-process-key>'
|
|
105
|
+
|
|
106
|
+
# READ a file
|
|
107
|
+
curl http://localhost:8321/api/wiki/{{workspace_name}}/files/<path> \
|
|
108
|
+
-H 'x-process-key: <your-process-key>'
|
|
109
|
+
|
|
110
|
+
# CREATE a file (POST — used by wiki.ingest_url, wiki.compile, wiki.ask)
|
|
111
|
+
curl http://localhost:8321/api/wiki/{{workspace_name}}/files/<path> \
|
|
112
|
+
-X POST \
|
|
113
|
+
-H 'content-type: application/json' \
|
|
114
|
+
-H 'x-process-key: <your-process-key>' \
|
|
115
|
+
-d '{"content":"..."}'
|
|
116
|
+
|
|
117
|
+
# APPEND/PREPEND to a file (PATCH — used for log.md, _index.md, taxonomy.md)
|
|
118
|
+
curl http://localhost:8321/api/wiki/{{workspace_name}}/files/<path> \
|
|
119
|
+
-X PATCH \
|
|
120
|
+
-H 'content-type: application/json' \
|
|
121
|
+
-H 'x-process-key: <your-process-key>' \
|
|
122
|
+
-d '{"mode":"append","content":"..."}'
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Filtering the index is a `jq` job — e.g. raw notes touched since a baseline ISO timestamp:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
curl http://localhost:8321/api/wiki/{{workspace_name}}/index \
|
|
129
|
+
-H 'x-process-key: <your-process-key>' \
|
|
130
|
+
| jq -r --arg since '<YYYY-MM-DDTHH:MM:SSZ>' \
|
|
131
|
+
'.files[] | select(.path | startswith("10_raw/")) | select(.mtime > $since) | .path'
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Common error codes
|
|
135
|
+
|
|
136
|
+
The shim emits `PA_API_ERROR {...}` to stderr on every non-2xx. React on `status` + `bodyPreview.error`:
|
|
137
|
+
|
|
138
|
+
- `403 missing_process_key` → add `-H 'x-process-key: ...'`.
|
|
139
|
+
- `403 raw_write_denied` / `wiki_write_denied` / `meta_write_denied` / `output_write_denied` / `log_write_denied` → your process key isn't authorized for that layer; fix the target path, not the header.
|
|
140
|
+
- `400 invalid_path` / `invalid_layer` → fix the slug shape or layer prefix.
|
|
141
|
+
- `400 invalid_body` → JSON shape wrong (e.g. `content` must be a string; PATCH `content` must be non-empty).
|
|
142
|
+
- `409 append_only` → file is immutable in this layer; either suffix the slug or use PATCH instead of POST.
|
|
143
|
+
- `413` → body exceeds the 512 KB cap.
|
|
144
|
+
|
|
145
|
+
If the Bash call returns to the prompt with no `PA_API_ERROR` and no body, your command did not start with literal `curl` and was denied under `dontAsk`. Rewrite as a flat curl invocation.
|
|
@@ -17,7 +17,18 @@ Note on coverage: routines whose calendar window is already covered by
|
|
|
17
17
|
double-fetch. The catalog only emits drift / retrospective / imminent
|
|
18
18
|
windows for the pre-pass.
|
|
19
19
|
|
|
20
|
-
POST every returned event to
|
|
20
|
+
POST every returned event — for a whole window in **one** call — to
|
|
21
|
+
`http://localhost:8321/api/observations/batch`. Build the body as:
|
|
22
|
+
|
|
23
|
+
```json
|
|
24
|
+
{"observations":[
|
|
25
|
+
{"source":"google_calendar:<calendarId>","ref":"<eventId>","changeType":"created","actor":"agent",
|
|
26
|
+
"payload":{"kind":"calendar","providerId":"<calendarId>","raw":{"title":"…","start":"…","end":"…","attendees":[…],"status":"…"}}},
|
|
27
|
+
…
|
|
28
|
+
]}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Field rules per element:
|
|
21
32
|
|
|
22
33
|
- `source` = `"google_calendar:<calendarId>"` (use `"primary"` when the
|
|
23
34
|
provider returns no explicit id)
|
|
@@ -31,13 +42,19 @@ POST every returned event to `http://localhost:8321/api/observations`:
|
|
|
31
42
|
"raw": { "title": ..., "start": ..., "end": ...,
|
|
32
43
|
"attendees": [...], "status": ... } }`
|
|
33
44
|
|
|
34
|
-
Server computes the dedup hash from `(source, payload)`.
|
|
45
|
+
Server computes the dedup hash from `(source, payload)`. The batch endpoint
|
|
46
|
+
always returns `200` with a JSON envelope `{ "results": [...], "fetched": N,
|
|
47
|
+
"posted": N, "duplicates": N, "errors": N }`. Per-item `results[*].status`:
|
|
48
|
+
|
|
49
|
+
- `"created"` / `"modified"` — rolled into `posted`.
|
|
50
|
+
- `"duplicate"` — rolled into `duplicates`.
|
|
51
|
+
- `"flip_locked"` — append `{type:"flip-locked","integration":"google_calendar"}`
|
|
52
|
+
to `errors` and continue.
|
|
53
|
+
- `"validation_error"` — append `{type:"validation-error","integration":"google_calendar","ref":"<ref>","detail":"<results[*].error>"}`
|
|
54
|
+
to `errors` and continue.
|
|
35
55
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
- `409 {error: "integration_flip_in_progress"}` — append
|
|
39
|
-
`{type:"flip-locked","integration":"google_calendar"}` to `errors`
|
|
40
|
-
and move on.
|
|
56
|
+
Cap each batch at 200 entries — split the window into multiple POSTs if the
|
|
57
|
+
upstream call returns more than that.
|
|
41
58
|
|
|
42
59
|
<!-- mode:direct:google_calendar -->
|
|
43
60
|
GET `http://localhost:8321/api/calendar/events<query>` where `<query>` is
|
|
@@ -45,7 +62,8 @@ the literal `query` attribute of the `<fetch>` row (e.g.
|
|
|
45
62
|
`?date=2026-05-11&days=1` or `?date=2026-05-04&days=7`). The route
|
|
46
63
|
accepts `date=YYYY-MM-DD` (or `today`) plus `days=N` (≤90); `timeMin`
|
|
47
64
|
/ `timeMax` are NOT recognised. The daemon returns `{ "events": [...] }`;
|
|
48
|
-
map
|
|
65
|
+
map every event into the `observations[]` array of a single
|
|
66
|
+
`POST /api/observations/batch` call.
|
|
49
67
|
<!-- /mode:direct:google_calendar -->
|
|
50
68
|
|
|
51
69
|
<!-- mode:delegated-same:google_calendar -->
|
|
@@ -100,7 +118,8 @@ following body (substitute the row's `query` into `task`):
|
|
|
100
118
|
}
|
|
101
119
|
```
|
|
102
120
|
|
|
103
|
-
Map
|
|
121
|
+
Map all items in `result.events[]` into a single
|
|
122
|
+
`POST /api/observations/batch` call.
|
|
104
123
|
<!-- /mode:delegated-cross:google_calendar -->
|
|
105
124
|
|
|
106
125
|
<!-- mode:native:google_calendar -->
|
|
@@ -28,7 +28,18 @@ the window via `<calendar_events_*>` (multi-provider after §6.6), the
|
|
|
28
28
|
catalog skips the pre-pass row to avoid double-fetching. The pre-pass
|
|
29
29
|
only ships drift / retrospective / imminent windows.
|
|
30
30
|
|
|
31
|
-
POST every returned event to
|
|
31
|
+
POST every returned event — for a whole window in **one** call — to
|
|
32
|
+
`http://localhost:8321/api/observations/batch`. Build the body as:
|
|
33
|
+
|
|
34
|
+
```json
|
|
35
|
+
{"observations":[
|
|
36
|
+
{"source":"outlook_calendar:<calendarId>","ref":"<eventId>","changeType":"created","actor":"agent",
|
|
37
|
+
"payload":{"kind":"calendar","providerId":"<calendarId>","raw":{"title":"…","start":"…","end":"…","attendees":[…],"status":"…"}}},
|
|
38
|
+
…
|
|
39
|
+
]}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Field rules per element:
|
|
32
43
|
|
|
33
44
|
- `source` = `"outlook_calendar:<calendarId>"` (use `"primary"` when
|
|
34
45
|
the provider returns no explicit id)
|
|
@@ -41,13 +52,19 @@ POST every returned event to `http://localhost:8321/api/observations`:
|
|
|
41
52
|
"raw": { "title": ..., "start": ..., "end": ...,
|
|
42
53
|
"attendees": [...], "status": ... } }`
|
|
43
54
|
|
|
44
|
-
Server computes the dedup hash from `(source, payload)`.
|
|
55
|
+
Server computes the dedup hash from `(source, payload)`. The batch endpoint
|
|
56
|
+
always returns `200` with a JSON envelope `{ "results": [...], "fetched": N,
|
|
57
|
+
"posted": N, "duplicates": N, "errors": N }`. Per-item `results[*].status`:
|
|
45
58
|
|
|
46
|
-
- `
|
|
47
|
-
- `
|
|
48
|
-
- `
|
|
49
|
-
|
|
50
|
-
|
|
59
|
+
- `"created"` / `"modified"` — rolled into `posted`.
|
|
60
|
+
- `"duplicate"` — rolled into `duplicates`.
|
|
61
|
+
- `"flip_locked"` — append `{type:"flip-locked","integration":"outlook_calendar"}`
|
|
62
|
+
to `errors` and continue.
|
|
63
|
+
- `"validation_error"` — append `{type:"validation-error","integration":"outlook_calendar","ref":"<ref>","detail":"<results[*].error>"}`
|
|
64
|
+
to `errors` and continue.
|
|
65
|
+
|
|
66
|
+
Cap each batch at 200 entries — split the window into multiple POSTs if the
|
|
67
|
+
upstream call returns more than that.
|
|
51
68
|
|
|
52
69
|
<!-- mode:direct:outlook_calendar -->
|
|
53
70
|
GET `http://localhost:8321/api/calendar/outlook/events<query>` where
|
|
@@ -55,8 +72,8 @@ GET `http://localhost:8321/api/calendar/outlook/events<query>` where
|
|
|
55
72
|
`?date=2026-05-11&days=1` or `?date=2026-05-04&days=7`). The route
|
|
56
73
|
accepts `date=YYYY-MM-DD` (or `today`) plus `days=N` (≤90); `timeMin`
|
|
57
74
|
/ `timeMax` are NOT recognised. The daemon returns
|
|
58
|
-
`{ "events": [...] }`; map
|
|
59
|
-
|
|
75
|
+
`{ "events": [...] }`; map every event into the `observations[]` array
|
|
76
|
+
of a single `POST /api/observations/batch` call.
|
|
60
77
|
<!-- /mode:direct:outlook_calendar -->
|
|
61
78
|
|
|
62
79
|
<!-- mode:delegated-same:outlook_calendar -->
|
|
@@ -8,37 +8,60 @@ spec: ROUTINE_DATA_ACQUISITION_DESIGN.md §6.8 / §8.1
|
|
|
8
8
|
|
|
9
9
|
For every `<fetch integration="gmail" ...>` row in `<acquisition-plan>`, take
|
|
10
10
|
the branch below that matches the row's `mode` attribute and acquire the
|
|
11
|
-
window described by `query`.
|
|
12
|
-
the query against that account only, do not pool across accounts.
|
|
11
|
+
window described by `query`.
|
|
13
12
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
**Account attribution.** In `direct` mode each row carries
|
|
14
|
+
`account="<accountId>"` — apply the query against THAT account only, do not
|
|
15
|
+
pool across accounts. In `delegated-same` / `delegated-cross` / `native` modes
|
|
16
|
+
the daemon emits a single row WITHOUT an `account` attribute because the
|
|
17
|
+
bound Gmail MCP authenticates as a single user; substitute the literal
|
|
18
|
+
string `default` wherever the observation contract below references
|
|
19
|
+
`<accountId>`. Never invent an accountId from the message body — `default`
|
|
20
|
+
is the canonical placeholder.
|
|
17
21
|
|
|
18
|
-
|
|
22
|
+
POST every returned message — for a whole window in **one** call — to
|
|
23
|
+
`http://localhost:8321/api/observations/batch` (the response from the
|
|
24
|
+
upstream call IS the payload; do not summarise or rank). Build the body as:
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
{"observations":[
|
|
28
|
+
{"source":"gmail:<accountId>","ref":"<messageId>","changeType":"created","actor":"agent",
|
|
29
|
+
"payload":{"kind":"mail","providerId":"<accountId>","raw":{"subject":"…","from":"…","snippet":"…","date":"…"}}},
|
|
30
|
+
…
|
|
31
|
+
]}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Field rules per element:
|
|
35
|
+
|
|
36
|
+
- `source` = `"gmail:<accountId>"` (use `"gmail:default"` when the
|
|
37
|
+
`<fetch>` row has no `account` attribute — see Account attribution above)
|
|
19
38
|
- `ref` = provider-side stable message id
|
|
20
39
|
- `changeType` = `"created"` for fresh items; `"modified"` when the row updates
|
|
21
40
|
a payload the server already has under the same `(source, ref)`
|
|
22
41
|
- `actor` = `"agent"`
|
|
23
42
|
- `payload` = `{ "kind": "mail", "providerId": "<accountId>", "raw": {
|
|
24
43
|
"subject": ..., "from": ..., "snippet": ...,
|
|
25
|
-
"date": ... } }`
|
|
44
|
+
"date": ... } }` (providerId is `"default"` when no
|
|
45
|
+
`account` attribute)
|
|
26
46
|
|
|
27
47
|
Do NOT compute the dedup hash — the server derives it from `(source, payload)`.
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
- `
|
|
35
|
-
|
|
36
|
-
`duplicates` and move on.
|
|
37
|
-
- `409 {error: "integration_flip_in_progress"}` — a mode flip is
|
|
38
|
-
draining for this integration. Record
|
|
48
|
+
|
|
49
|
+
The batch endpoint always returns `200` with a JSON envelope `{ "results": [...],
|
|
50
|
+
"fetched": N, "posted": N, "duplicates": N, "errors": N }`. Add each
|
|
51
|
+
field-count into your top-level totals. Per-item `results[*].status` values:
|
|
52
|
+
|
|
53
|
+
- `"created"` / `"modified"` — fresh or updated row; rolled into `posted`.
|
|
54
|
+
- `"duplicate"` — identical pending row already exists; rolled into `duplicates`.
|
|
55
|
+
- `"flip_locked"` — a mode flip is draining for this integration. Append
|
|
39
56
|
`{type:"flip-locked","integration":"gmail","account":"<accountId>"}`
|
|
40
|
-
|
|
41
|
-
retry on the next tick).
|
|
57
|
+
(use `"default"` when no `account` attribute) to your `errors` array
|
|
58
|
+
and continue (the parent routine will retry on the next tick).
|
|
59
|
+
- `"validation_error"` — a malformed item slipped through. Append
|
|
60
|
+
`{type:"validation-error","integration":"gmail","account":"<accountId>","ref":"<ref>","detail":"<results[*].error>"}`
|
|
61
|
+
to `errors` and continue.
|
|
62
|
+
|
|
63
|
+
Cap each batch at 200 entries — split the window into multiple POSTs if the
|
|
64
|
+
upstream call returns more than that.
|
|
42
65
|
|
|
43
66
|
<!-- mode:direct:gmail -->
|
|
44
67
|
GET `http://localhost:8321/api/mail/<accountId>/messages<query>` where
|
|
@@ -48,7 +71,8 @@ GET `http://localhost:8321/api/mail/<accountId>/messages<query>` where
|
|
|
48
71
|
`?folder=sent&since=2026-05-11T00:00:00.000Z&limit=30`). The route
|
|
49
72
|
accepts `since` (ISO 8601 datetime), `limit`, `folder`, `q`,
|
|
50
73
|
`unreadOnly` — `days=…` is NOT recognised. The daemon returns
|
|
51
|
-
`{ "messages": [...] }`; map
|
|
74
|
+
`{ "messages": [...] }`; map every message into the `observations[]`
|
|
75
|
+
array of a single `POST /api/observations/batch` call.
|
|
52
76
|
<!-- /mode:direct:gmail -->
|
|
53
77
|
|
|
54
78
|
<!-- mode:delegated-same:gmail -->
|
|
@@ -64,11 +88,13 @@ does not proxy in this branch.
|
|
|
64
88
|
The connector is bound to a different backend than this session, so reach it
|
|
65
89
|
through the daemon's delegation proxy. POST to
|
|
66
90
|
`http://localhost:8321/api/integrations/gmail/exec` with the following body
|
|
67
|
-
(substitute the row's `query`
|
|
91
|
+
(substitute the row's `query` into `task`). The `<fetch>` row in this mode
|
|
92
|
+
carries no `account` attribute — the proxy's bound MCP authenticates as a
|
|
93
|
+
single user, so the task is account-implicit:
|
|
68
94
|
|
|
69
95
|
```json
|
|
70
96
|
{
|
|
71
|
-
"task": "
|
|
97
|
+
"task": "Search Gmail with the query expression <query> and return id, subject, from, snippet, date for each message. Up to 30 messages.",
|
|
72
98
|
"outputSchema": {
|
|
73
99
|
"type": "object",
|
|
74
100
|
"required": ["messages"],
|
|
@@ -94,7 +120,8 @@ through the daemon's delegation proxy. POST to
|
|
|
94
120
|
}
|
|
95
121
|
```
|
|
96
122
|
|
|
97
|
-
Map
|
|
123
|
+
Map all items in `result.messages[]` into a single
|
|
124
|
+
`POST /api/observations/batch` call.
|
|
98
125
|
<!-- /mode:delegated-cross:gmail -->
|
|
99
126
|
|
|
100
127
|
<!-- mode:native:gmail -->
|
|
@@ -7,8 +7,16 @@ spec: ROUTINE_DATA_ACQUISITION_DESIGN.md §6.8 / §8.2
|
|
|
7
7
|
# Outlook Mail acquisition
|
|
8
8
|
|
|
9
9
|
For every `<fetch integration="outlook_mail" ...>` row in `<acquisition-plan>`,
|
|
10
|
-
take the branch below that matches the row's `mode` attribute.
|
|
11
|
-
|
|
10
|
+
take the branch below that matches the row's `mode` attribute.
|
|
11
|
+
|
|
12
|
+
**Account attribution.** In `direct` mode each row carries
|
|
13
|
+
`account="<accountId>"` — apply the query against THAT account only. In
|
|
14
|
+
`delegated-same` / `delegated-cross` / `native` modes the daemon emits a
|
|
15
|
+
single row WITHOUT an `account` attribute (the bound MCP authenticates as
|
|
16
|
+
one user); substitute the literal string `default` wherever the observation
|
|
17
|
+
contract below references `<accountId>`. The `userManagedConnector`
|
|
18
|
+
collapse (see below) means `delegated-cross` never carries an account
|
|
19
|
+
either.
|
|
12
20
|
|
|
13
21
|
Outlook Mail is a **user-managed** integration: the daemon has no
|
|
14
22
|
delegation proxy for it (no `/api/integrations/outlook_mail/exec` exists).
|
|
@@ -22,25 +30,47 @@ The four non-disabled branches therefore split into two real flows:
|
|
|
22
30
|
this partial states the intent, not specific tool names. If no surface
|
|
23
31
|
is bound, record an error and continue.
|
|
24
32
|
|
|
25
|
-
POST every returned message to
|
|
33
|
+
POST every returned message — for a whole window in **one** call — to
|
|
34
|
+
`http://localhost:8321/api/observations/batch`. Build the body as:
|
|
35
|
+
|
|
36
|
+
```json
|
|
37
|
+
{"observations":[
|
|
38
|
+
{"source":"outlook_mail:<accountId>","ref":"<messageId>","changeType":"created","actor":"agent",
|
|
39
|
+
"payload":{"kind":"mail","providerId":"<accountId>","raw":{"subject":"…","from":"…","snippet":"…","date":"…"}}},
|
|
40
|
+
…
|
|
41
|
+
]}
|
|
42
|
+
```
|
|
26
43
|
|
|
27
|
-
|
|
44
|
+
Field rules per element:
|
|
45
|
+
|
|
46
|
+
- `source` = `"outlook_mail:<accountId>"` (use `"outlook_mail:default"`
|
|
47
|
+
when the `<fetch>` row has no `account` attribute — see Account
|
|
48
|
+
attribution above)
|
|
28
49
|
- `ref` = provider-side stable message id
|
|
29
50
|
- `changeType` = `"created"` for fresh items; `"modified"` when the row
|
|
30
51
|
updates a payload already known under `(source, ref)`
|
|
31
52
|
- `actor` = `"agent"`
|
|
32
53
|
- `payload` = `{ "kind": "mail", "providerId": "<accountId>", "raw": {
|
|
33
54
|
"subject": ..., "from": ..., "snippet": ...,
|
|
34
|
-
"date": ... } }`
|
|
55
|
+
"date": ... } }` (providerId is `"default"` when no
|
|
56
|
+
`account` attribute)
|
|
35
57
|
|
|
36
|
-
The server computes the dedup hash from `(source, payload)`.
|
|
37
|
-
|
|
58
|
+
The server computes the dedup hash from `(source, payload)`. The batch
|
|
59
|
+
endpoint always returns `200` with `{ "results": [...], "fetched": N,
|
|
60
|
+
"posted": N, "duplicates": N, "errors": N }`. Per-item `results[*].status`:
|
|
38
61
|
|
|
39
|
-
- `
|
|
40
|
-
- `
|
|
41
|
-
- `
|
|
62
|
+
- `"created"` / `"modified"` — rolled into `posted`.
|
|
63
|
+
- `"duplicate"` — rolled into `duplicates`.
|
|
64
|
+
- `"flip_locked"` — append
|
|
42
65
|
`{type:"flip-locked","integration":"outlook_mail","account":"<accountId>"}`
|
|
43
|
-
|
|
66
|
+
(use `"default"` when no `account` attribute) to `errors` and
|
|
67
|
+
continue; do not retry inline.
|
|
68
|
+
- `"validation_error"` — append
|
|
69
|
+
`{type:"validation-error","integration":"outlook_mail","account":"<accountId>","ref":"<ref>","detail":"<results[*].error>"}`
|
|
70
|
+
to `errors` and continue.
|
|
71
|
+
|
|
72
|
+
Cap each batch at 200 entries — split the window into multiple POSTs if the
|
|
73
|
+
upstream call returns more than that.
|
|
44
74
|
|
|
45
75
|
<!-- mode:direct:outlook_mail -->
|
|
46
76
|
GET `http://localhost:8321/api/mail/<accountId>/messages<query>` where
|
|
@@ -49,8 +79,8 @@ GET `http://localhost:8321/api/mail/<accountId>/messages<query>` where
|
|
|
49
79
|
`?folder=sent&since=2026-05-11T00:00:00.000Z&limit=30`). The route
|
|
50
80
|
accepts `since` (ISO 8601), `limit`, `folder`, `q`, `unreadOnly` — it
|
|
51
81
|
does NOT accept `days=…`. The daemon returns `{ "messages": [...] }`
|
|
52
|
-
regardless of the underlying provider; map
|
|
53
|
-
|
|
82
|
+
regardless of the underlying provider; map every message into the
|
|
83
|
+
`observations[]` array of a single `POST /api/observations/batch` call.
|
|
54
84
|
<!-- /mode:direct:outlook_mail -->
|
|
55
85
|
|
|
56
86
|
<!-- mode:delegated-same:outlook_mail -->
|
|
@@ -62,7 +92,7 @@ args your bound surface accepts. POST every returned message as specified
|
|
|
62
92
|
above.
|
|
63
93
|
|
|
64
94
|
If no Outlook Mail surface is bound on this backend, append
|
|
65
|
-
`{"type":"no-surface","integration":"outlook_mail","account":"<accountId>"}`
|
|
95
|
+
`{"type":"no-surface","integration":"outlook_mail","account":"<accountId>"}` (use `"default"` when no `account` attribute)
|
|
66
96
|
to your `errors` array and continue with the next row. Do NOT halt the
|
|
67
97
|
pre-pass; the parent routine continues with whatever observations the rest
|
|
68
98
|
of the plan produced.
|
|
@@ -74,7 +104,7 @@ proxy. The dispatcher should not have emitted a `delegated-cross` row for
|
|
|
74
104
|
this integration — if you see one, treat it exactly like
|
|
75
105
|
`delegated-same`: use whichever in-session surface your skills document
|
|
76
106
|
for Outlook Mail. If nothing is bound, append
|
|
77
|
-
`{"type":"no-surface","integration":"outlook_mail","account":"<accountId>"}`
|
|
107
|
+
`{"type":"no-surface","integration":"outlook_mail","account":"<accountId>"}` (use `"default"` when no `account` attribute)
|
|
78
108
|
to `errors` and continue.
|
|
79
109
|
<!-- /mode:delegated-cross:outlook_mail -->
|
|
80
110
|
|
|
@@ -84,7 +114,7 @@ in-session connector surface your skills document for Outlook Mail —
|
|
|
84
114
|
same call shape as `delegated-same`. The daemon does not proxy.
|
|
85
115
|
|
|
86
116
|
If no Outlook Mail surface is bound, append
|
|
87
|
-
`{"type":"no-surface","integration":"outlook_mail","account":"<accountId>"}`
|
|
117
|
+
`{"type":"no-surface","integration":"outlook_mail","account":"<accountId>"}` (use `"default"` when no `account` attribute)
|
|
88
118
|
to `errors` and continue.
|
|
89
119
|
<!-- /mode:native:outlook_mail -->
|
|
90
120
|
|
|
@@ -10,7 +10,18 @@ For every `<fetch integration="notion" ...>` row in `<acquisition-plan>`,
|
|
|
10
10
|
take the branch below that matches the row's `mode` attribute. Notion rows
|
|
11
11
|
do not fan out per account — the dispatcher emits one row per workspace.
|
|
12
12
|
|
|
13
|
-
POST every returned page to
|
|
13
|
+
POST every returned page — for a whole window in **one** call — to
|
|
14
|
+
`http://localhost:8321/api/observations/batch`. Build the body as:
|
|
15
|
+
|
|
16
|
+
```json
|
|
17
|
+
{"observations":[
|
|
18
|
+
{"source":"notion:<workspaceId>","ref":"<pageId>","changeType":"created","actor":"agent",
|
|
19
|
+
"payload":{"kind":"notion","providerId":"<workspaceId>","raw":{"title":"…","last_edited":"…","parent":"…","url":"…"}}},
|
|
20
|
+
…
|
|
21
|
+
]}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Field rules per element:
|
|
14
25
|
|
|
15
26
|
- `source` = `"notion:<workspaceId>"` (use `"default"` when the daemon
|
|
16
27
|
reports no explicit workspace id)
|
|
@@ -22,12 +33,19 @@ POST every returned page to `http://localhost:8321/api/observations`:
|
|
|
22
33
|
"raw": { "title": ..., "last_edited": ...,
|
|
23
34
|
"parent": ..., "url": ... } }`
|
|
24
35
|
|
|
25
|
-
The server computes the dedup hash from `(source, payload)`.
|
|
36
|
+
The server computes the dedup hash from `(source, payload)`. The batch
|
|
37
|
+
endpoint always returns `200` with `{ "results": [...], "fetched": N,
|
|
38
|
+
"posted": N, "duplicates": N, "errors": N }`. Per-item `results[*].status`:
|
|
39
|
+
|
|
40
|
+
- `"created"` / `"modified"` — rolled into `posted`.
|
|
41
|
+
- `"duplicate"` — rolled into `duplicates`.
|
|
42
|
+
- `"flip_locked"` — append `{type:"flip-locked","integration":"notion"}`
|
|
43
|
+
to `errors` and continue.
|
|
44
|
+
- `"validation_error"` — append `{type:"validation-error","integration":"notion","ref":"<ref>","detail":"<results[*].error>"}`
|
|
45
|
+
to `errors` and continue.
|
|
26
46
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
- `409 {error: "integration_flip_in_progress"}` — append
|
|
30
|
-
`{type:"flip-locked","integration":"notion"}` to `errors` and move on.
|
|
47
|
+
Cap each batch at 200 entries — split the window into multiple POSTs if the
|
|
48
|
+
upstream call returns more than that.
|
|
31
49
|
|
|
32
50
|
<!-- mode:direct:notion -->
|
|
33
51
|
GET `http://localhost:8321/api/notion/search<query>` where `<query>` is
|
|
@@ -39,8 +57,9 @@ window cutoff client-side. The daemon returns `{ "results": [...] }`
|
|
|
39
57
|
sorted by `last_edited_time` descending; filter to entries whose
|
|
40
58
|
`last_edited_time` is at or after the window the `<fetch>` row's
|
|
41
59
|
window symbol implies (`updated_24h` → today's agent-day start,
|
|
42
|
-
`updated_1h` → the current hour boundary), then map
|
|
43
|
-
page into
|
|
60
|
+
`updated_1h` → the current hour boundary), then map every surviving
|
|
61
|
+
page into the `observations[]` array of a single
|
|
62
|
+
`POST /api/observations/batch` call.
|
|
44
63
|
<!-- /mode:direct:notion -->
|
|
45
64
|
|
|
46
65
|
<!-- mode:delegated-same:notion -->
|
|
@@ -85,7 +104,8 @@ body (substitute the row's `query` into `task`):
|
|
|
85
104
|
}
|
|
86
105
|
```
|
|
87
106
|
|
|
88
|
-
Map
|
|
107
|
+
Map all items in `result.pages[]` into a single
|
|
108
|
+
`POST /api/observations/batch` call.
|
|
89
109
|
<!-- /mode:delegated-cross:notion -->
|
|
90
110
|
|
|
91
111
|
<!-- mode:native:notion -->
|
|
@@ -36,6 +36,22 @@ If any gate fails, leave the latent entry untouched. The user must never feel th
|
|
|
36
36
|
|
|
37
37
|
Apply the conversational profile's "speak as one agent" rule: phrase your knowledge as your own memory; never name internal storage, sections, files, or routines in user-visible text. The user-facing message discipline (awareness, no ceremony, no readback, compactness) is owned by the notify skill.
|
|
38
38
|
|
|
39
|
+
#### No topic-pivoting trailing question (universal, non-negotiable)
|
|
40
|
+
|
|
41
|
+
Never append a question that **changes the topic** of the user's message. A topic-continuing question (clarifier, follow-up, "want me to track this as X?" that flows from what the user just said) is fine when it reads as a natural beat in the same thread. A topic-pivoting question — about an unrelated task, deadline, or profile slot — is forbidden in the same reply, even if a gate's conditions for that ask are technically met.
|
|
42
|
+
|
|
43
|
+
If the reply does not naturally invite a question of its own, end with a statement. The agent has other surfaces (the morning briefing, the `scheduled.dm` confirm sub-flow, observation alerts) for non-conversational asks — the inbound DM reply is not the only channel, and pushing them here costs the conversation more than it saves the agent.
|
|
44
|
+
|
|
45
|
+
Worked examples (illustrate the topic-continuing vs. topic-pivoting distinction; tone follows persona / Character, NOT these example phrasings):
|
|
46
|
+
|
|
47
|
+
| User opener | Acceptable trailing question | Forbidden trailing question |
|
|
48
|
+
|---|---|---|
|
|
49
|
+
| "I quit IBM Japan, moved to LA for a PM master's." | *"Want me to start tracking the program — syllabus, deadlines — as a project?"* (continues the share) | *"By the way, your 23:59 PT 407632 midterm is tomorrow — how's prep?"* (pivots to an unrelated deadline) |
|
|
50
|
+
| "Feeling pretty drained today." | *(no trailing question — acknowledge the mood)* | *"What time should I remind you about the design review?"* (ignores the mood) |
|
|
51
|
+
| "What's on today?" | *"Want a heads-up 15 min before the 2pm review?"* (continues the orientation) | *"Also — what city are you in these days?"* (latent-profile weave on a tight factual ask) |
|
|
52
|
+
|
|
53
|
+
This rule covers the latent-profile-question weave in Step 2 and any future opportunistic ask. **When a gate would have asked here but this rule suppresses it, the gate SHOULD instead schedule a `confirm:` sub-flow row** (see `scheduled.dm.md` ## Confirmation follow-up) so the question lands at a natural moment without violating the thread.
|
|
54
|
+
|
|
39
55
|
**Day-type filter.** Parse line 2 of <today>. For any category whose focus is `off` (map via the today skill's "Category → focus-dimension mapping"), do not volunteer items in that category.
|
|
40
56
|
|
|
41
57
|
**Resolved User Tasks.** When the user reports completing one of their tasks, mark it `[x]` per the today / context skill. Do NOT modify the agent's internal Agent Plan rows from this handler — those flip in `scheduled.task` handlers and Evening Review only.
|
|
@@ -116,6 +132,23 @@ If a tool you genuinely need is denied, or no available tool can handle the file
|
|
|
116
132
|
|
|
117
133
|
These dispatchers are not exclusive — multiple may apply to one message.
|
|
118
134
|
|
|
135
|
+
**Confirm-reply continuation.** Before evaluating the per-domain
|
|
136
|
+
dispatchers below, scan `<conversation_history>` for the most recent
|
|
137
|
+
assistant message. If that message asked a question that the user is
|
|
138
|
+
now answering (typical shape: a short, single-question DM with no
|
|
139
|
+
project/task name embedded — emitted by the `scheduled.dm.md` ## Confirmation
|
|
140
|
+
follow-up sub-flow), route the user's reply to the originating gate's
|
|
141
|
+
**reply branches** based on the topic of the question — *not* on the
|
|
142
|
+
literal text of the user's reply. A bare "yeah" or "no" from the user
|
|
143
|
+
will not match the named-workstream / commitment shape the per-domain
|
|
144
|
+
dispatchers expect, so without this routing rule the reply would
|
|
145
|
+
silently miss the gate. Concretely: a confirm DM about *"track the LA
|
|
146
|
+
PM master's as a project?"* → user replies "yeah" / "no" / "actually
|
|
147
|
+
call it la-pm" → route through the context skill's "Project DM-intent
|
|
148
|
+
detection" §"Reply branches", carrying the user's reply shape (yes /
|
|
149
|
+
counter-proposal / no) into that handler. Apply the same pattern to
|
|
150
|
+
any other gate that opts into the confirm sub-flow in the future.
|
|
151
|
+
|
|
119
152
|
**Scheduling.** Recurring ("every morning at 9", "weekly") → `POST /api/recurring-schedules`. One-shot ("tomorrow 3pm", "in 30 min") → `POST /api/schedule/dm` (pre-composed; default) or `POST /api/schedule` (wake-up that must look something up at fire time). Edit / cancel / list use the same endpoints. Load the schedule skill for the request shape, dedup pre-check, DM-vs-wake decision, and description contract. Use `<current_time>` for timezone resolution.
|
|
120
153
|
|
|
121
154
|
Schedules go through this daemon — never through any cloud-hosted scheduled-agent feature your CLI may expose. Cloud routines cannot reach `localhost:8321`, so they cannot deliver via the user's chat platforms or use any integration registered here. Do not propose one as a tradeoff.
|
|
@@ -130,9 +163,9 @@ Schedules go through this daemon — never through any cloud-hosted scheduled-ag
|
|
|
130
163
|
- **Re-enable** → `PATCH /api/recurring-schedules/:id` `{"enabled": true}`.
|
|
131
164
|
3. Confirm to the user in persona voice. Keep it short — never name internal mechanisms ("recurring schedule", "pin_to_quiet_hours_end", row IDs) in user-visible text.
|
|
132
165
|
|
|
133
|
-
**Long-horizon intent** (commitment, trip, deliverable, learning target beyond today) → route through the roadmap skill's "Long-horizon DM-intent detection". Ambiguous or speculative items belong in `agent
|
|
166
|
+
**Long-horizon intent** (commitment, trip, deliverable, learning target beyond today) → route through the roadmap skill's "Long-horizon DM-intent detection". Ambiguous or speculative items belong in `agent/journal.md` as a candidate line for the next morning routine to confirm — do **not** write directly to `roadmap.md` without a clear positive signal.
|
|
134
167
|
|
|
135
|
-
**Project intent** (state, progress, milestone, blocker, or a new-project request for a named workstream) → route through the context skill's "Project DM-intent detection". A new project requires the
|
|
168
|
+
**Project intent** (state, progress, milestone, blocker, or a new-project request for a named workstream) → route through the context skill's "Project DM-intent detection". A new project requires the project-creation gate before any write; the gate's "No match" path schedules a `confirm:` DM rather than asking inline (see the context skill Step 3 and `scheduled.dm.md` ## Confirmation follow-up). Silently inferring a slug is forbidden. A project update tied to a dated milestone runs both this dispatcher and the long-horizon one (see the context skill's "Tie-breakers"). Future durable-state domains (e.g. git) follow the same shape — per-domain skill block, thin dispatcher here.
|
|
136
169
|
|
|
137
170
|
## User Message
|
|
138
171
|
Platform: {event_data[platform]}
|