@aitne-sh/aitne 0.1.2 → 0.1.4

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.
Files changed (91) hide show
  1. package/README.md +151 -147
  2. package/agent-assets/agent-profiles/conversational.md +23 -1
  3. package/agent-assets/agent-profiles/observer.md +15 -0
  4. package/agent-assets/agent-profiles/routine-fetch-window.md +128 -0
  5. package/agent-assets/agent-profiles/routine.md +16 -0
  6. package/agent-assets/agent-profiles/task.md +15 -0
  7. package/agent-assets/docs/concepts/auth-health.md +25 -9
  8. package/agent-assets/docs/concepts/backends-and-tiers.md +40 -4
  9. package/agent-assets/docs/concepts/costs-and-quotas.md +87 -25
  10. package/agent-assets/docs/concepts/delegated-mode.md +7 -13
  11. package/agent-assets/docs/concepts/memory-model.md +14 -1
  12. package/agent-assets/docs/concepts/observations.md +19 -1
  13. package/agent-assets/docs/concepts/process-keys.md +5 -0
  14. package/agent-assets/docs/concepts/routines.md +22 -10
  15. package/agent-assets/docs/concepts/safety-model.md +3 -8
  16. package/agent-assets/docs/concepts/skills.md +36 -1
  17. package/agent-assets/docs/features/integrations/calendar.md +74 -3
  18. package/agent-assets/docs/features/integrations/git.md +4 -4
  19. package/agent-assets/docs/features/integrations/github.md +75 -107
  20. package/agent-assets/docs/features/lifestyle/git.md +169 -22
  21. package/agent-assets/docs/features/messaging/overview.md +10 -1
  22. package/agent-assets/docs/features/routines/morning-routine.md +1 -1
  23. package/agent-assets/docs/getting-started/01-what-is-this.md +30 -12
  24. package/agent-assets/docs/getting-started/02-first-steps.md +15 -4
  25. package/agent-assets/docs/getting-started/03-what-can-this-do.md +17 -2
  26. package/agent-assets/docs/guides/install-and-run.md +10 -1
  27. package/agent-assets/docs/guides/setup-wizard.md +43 -6
  28. package/agent-assets/docs/guides/switch-default-backend.md +7 -3
  29. package/agent-assets/docs/reference/skills.md +10 -1
  30. package/agent-assets/docs/troubleshooting/auth-failed.md +27 -8
  31. package/agent-assets/docs/troubleshooting/quota-exhausted.md +35 -12
  32. package/agent-assets/skills/context/SKILL.md +6 -0
  33. package/agent-assets/skills/external-services/SKILL.md +4 -0
  34. package/agent-assets/skills/external-services/SKILL.native.claude.md +320 -0
  35. package/agent-assets/skills/external-services/SKILL.native.codex.md +243 -0
  36. package/agent-assets/skills/external-services/SKILL.native.gemini.md +237 -0
  37. package/agent-assets/skills/mail/SKILL.md +42 -14
  38. package/agent-assets/skills/mail/SKILL.native.claude.md +175 -0
  39. package/agent-assets/skills/mail/SKILL.native.codex.md +165 -0
  40. package/agent-assets/skills/mail/SKILL.native.gemini.md +169 -0
  41. package/agent-assets/skills/management-task-modify/SKILL.md +2 -1
  42. package/agent-assets/skills/management-task-stop/SKILL.md +2 -2
  43. package/agent-assets/skills/notify/SKILL.md +4 -4
  44. package/agent-assets/skills/notion/SKILL.md +6 -0
  45. package/agent-assets/skills/notion/SKILL.native.claude.md +202 -0
  46. package/agent-assets/skills/notion/SKILL.native.codex.md +166 -0
  47. package/agent-assets/skills/notion/SKILL.native.gemini.md +167 -0
  48. package/agent-assets/skills/observations/SKILL.md +7 -0
  49. package/agent-assets/skills/project-doc/SKILL.md +6 -0
  50. package/agent-assets/skills/reading/SKILL.md +2 -0
  51. package/agent-assets/skills/roadmap/SKILL.md +7 -0
  52. package/agent-assets/skills/today/SKILL.md +7 -0
  53. package/agent-assets/skills/user-interview/SKILL.md +1 -1
  54. package/agent-assets/skills/user-profile/SKILL.md +7 -0
  55. package/agent-assets/task-flows/_partials/calendar-acquire.google_calendar.md +119 -0
  56. package/agent-assets/task-flows/_partials/calendar-acquire.outlook_calendar.md +101 -0
  57. package/agent-assets/task-flows/_partials/mail-acquire.gmail.md +113 -0
  58. package/agent-assets/task-flows/_partials/mail-acquire.outlook_mail.md +97 -0
  59. package/agent-assets/task-flows/_partials/notion-acquire.notion.md +104 -0
  60. package/agent-assets/task-flows/git.project.refresh_architecture.md +24 -1
  61. package/agent-assets/task-flows/message.received.dm.md +3 -0
  62. package/agent-assets/task-flows/message.received.dm.native.claude.md +76 -0
  63. package/agent-assets/task-flows/message.received.dm.native.codex.md +57 -0
  64. package/agent-assets/task-flows/message.received.dm.native.gemini.md +70 -0
  65. package/agent-assets/task-flows/message.received.dm_first.md +3 -0
  66. package/agent-assets/task-flows/message.received.dm_first.native.claude.md +56 -0
  67. package/agent-assets/task-flows/message.received.dm_first.native.codex.md +48 -0
  68. package/agent-assets/task-flows/message.received.dm_first.native.gemini.md +54 -0
  69. package/agent-assets/task-flows/routine.evening_review.md +28 -1
  70. package/agent-assets/task-flows/routine.fetch_window.md +93 -0
  71. package/agent-assets/task-flows/routine.hourly_check.md +44 -5
  72. package/agent-assets/task-flows/routine.monthly_review.md +13 -2
  73. package/agent-assets/task-flows/routine.morning_routine.md +55 -42
  74. package/agent-assets/task-flows/routine.morning_routine_initial.md +37 -38
  75. package/agent-assets/task-flows/routine.roadmap_refresh.md +38 -46
  76. package/agent-assets/task-flows/routine.today_refresh.md +53 -96
  77. package/agent-assets/task-flows/routine.weekly_review.md +40 -17
  78. package/agent-assets/task-flows/scheduled.dm.md +13 -11
  79. package/agent-assets/task-flows/scheduled.task.md +2 -2
  80. package/agent-assets/task-flows/setup.initial.md +5 -4
  81. package/agent-assets/task-flows/setup.update.md +1 -1
  82. package/agent-assets/templates/README.md +13 -6
  83. package/bin/aitne.mjs +1 -1
  84. package/package.json +22 -39
  85. package/scripts/check-redaction-coverage.mjs +0 -0
  86. package/scripts/message-discipline-digest.mjs +0 -0
  87. package/scripts/regen-skill-fixtures.mjs +39 -0
  88. package/scripts/remint-roadmap-ids.mjs +0 -0
  89. package/agent-assets/task-flows/routine.hourly_check.delegated.claude.md +0 -405
  90. package/agent-assets/task-flows/routine.hourly_check.delegated.codex.md +0 -400
  91. package/agent-assets/task-flows/routine.hourly_check.delegated.gemini.md +0 -404
@@ -0,0 +1,119 @@
1
+ ---
2
+ name: calendar-acquire.google_calendar
3
+ description: Acquire a Google Calendar event window per <acquisition-plan> row.
4
+ spec: ROUTINE_DATA_ACQUISITION_DESIGN.md §6.8 / §8.3
5
+ ---
6
+
7
+ # Google Calendar acquisition
8
+
9
+ For every `<fetch integration="google_calendar" ...>` row in
10
+ `<acquisition-plan>`, take the branch below that matches the row's `mode`
11
+ attribute. Calendar rows do not fan out per account today — the dispatcher
12
+ emits one row per active provider, scoped to the bound primary calendar.
13
+
14
+ Note on coverage: routines whose calendar window is already covered by
15
+ `ContextBuilder.buildCalendarBlock` (the `<calendar_events_*>` blocks) do
16
+ **not** appear in the pre-pass plan for the same window — that would
17
+ double-fetch. The catalog only emits drift / retrospective / imminent
18
+ windows for the pre-pass.
19
+
20
+ POST every returned event to `http://localhost:8321/api/observations`:
21
+
22
+ - `source` = `"google_calendar:<calendarId>"` (use `"primary"` when the
23
+ provider returns no explicit id)
24
+ - `ref` = provider-side stable event id
25
+ - `changeType` = `"created"` for fresh events; `"modified"` when the
26
+ payload updates an existing `(source, ref)`; `"deleted"` for cancelled
27
+ events (payload = `{ "kind": "calendar", "providerId": "<calendarId>",
28
+ "raw": { "deletedAt": "<iso>" } }`)
29
+ - `actor` = `"agent"`
30
+ - `payload` = `{ "kind": "calendar", "providerId": "<calendarId>",
31
+ "raw": { "title": ..., "start": ..., "end": ...,
32
+ "attendees": [...], "status": ... } }`
33
+
34
+ Server computes the dedup hash from `(source, payload)`. Response shape:
35
+
36
+ - `200 {action: "created"|"modified"}` — fresh / updated row; count in `posted`.
37
+ - `409 {error: "duplicate"}` — identical payload already pending; count in `duplicates`.
38
+ - `409 {error: "integration_flip_in_progress"}` — append
39
+ `{type:"flip-locked","integration":"google_calendar"}` to `errors`
40
+ and move on.
41
+
42
+ <!-- mode:direct:google_calendar -->
43
+ GET `http://localhost:8321/api/calendar/events<query>` where `<query>` is
44
+ the literal `query` attribute of the `<fetch>` row (e.g.
45
+ `?date=2026-05-11&days=1` or `?date=2026-05-04&days=7`). The route
46
+ accepts `date=YYYY-MM-DD` (or `today`) plus `days=N` (≤90); `timeMin`
47
+ / `timeMax` are NOT recognised. The daemon returns `{ "events": [...] }`;
48
+ map each event into one observation POST as specified above.
49
+ <!-- /mode:direct:google_calendar -->
50
+
51
+ <!-- mode:delegated-same:google_calendar -->
52
+ The connector is bound to your own session backend. Use the in-session
53
+ connector surface your skills document; the `<fetch>` row's `query`
54
+ attribute carries the catalog's `delegated` form (e.g.
55
+ `timeMin="..." timeMax="..." maxResults=50`). Translate it into the args
56
+ your bound surface accepts. POST every returned event as specified above.
57
+ <!-- /mode:delegated-same:google_calendar -->
58
+
59
+ <!-- mode:delegated-cross:google_calendar -->
60
+ The connector is bound to a different backend than this session — reach
61
+ it through the daemon's delegation proxy. POST to
62
+ `http://localhost:8321/api/integrations/google_calendar/exec` with the
63
+ following body (substitute the row's `query` into `task`):
64
+
65
+ ```json
66
+ {
67
+ "task": "List Google Calendar events for the window <query>. Return id, title, start, end, attendees (email + responseStatus), and status. Up to 100 events.",
68
+ "outputSchema": {
69
+ "type": "object",
70
+ "required": ["events"],
71
+ "properties": {
72
+ "events": {
73
+ "type": "array",
74
+ "items": {
75
+ "type": "object",
76
+ "required": ["id"],
77
+ "properties": {
78
+ "id": { "type": "string" },
79
+ "title": { "type": "string" },
80
+ "start": { "type": "string" },
81
+ "end": { "type": "string" },
82
+ "status": { "type": "string" },
83
+ "attendees": {
84
+ "type": "array",
85
+ "items": {
86
+ "type": "object",
87
+ "properties": {
88
+ "email": { "type": "string" },
89
+ "responseStatus": { "type": "string" }
90
+ }
91
+ }
92
+ }
93
+ }
94
+ }
95
+ }
96
+ }
97
+ },
98
+ "maxToolCalls": 3,
99
+ "cacheable": true
100
+ }
101
+ ```
102
+
103
+ Map each item in `result.events[]` to one observation POST.
104
+ <!-- /mode:delegated-cross:google_calendar -->
105
+
106
+ <!-- mode:native:google_calendar -->
107
+ The connector is bound natively to your own session backend. Use the
108
+ in-session connector surface your skills document — same call shape as
109
+ `delegated-same`. The daemon does not proxy. POST every returned event as
110
+ specified above.
111
+ <!-- /mode:native:google_calendar -->
112
+
113
+ <!-- mode:disabled:google_calendar -->
114
+ Defensive no-op. The dispatcher filters disabled integrations out of
115
+ `<acquisition-plan>`. If a `<fetch integration="google_calendar">` row
116
+ still reaches this branch, skip it and append
117
+ `{"type":"unexpected-row","integration":"google_calendar","reason":"disabled-row-emitted"}`
118
+ to your `errors` array.
119
+ <!-- /mode:disabled:google_calendar -->
@@ -0,0 +1,101 @@
1
+ ---
2
+ name: calendar-acquire.outlook_calendar
3
+ description: Acquire an Outlook Calendar event window per <acquisition-plan> row.
4
+ spec: ROUTINE_DATA_ACQUISITION_DESIGN.md §6.8 / §8.4
5
+ ---
6
+
7
+ # Outlook Calendar acquisition
8
+
9
+ For every `<fetch integration="outlook_calendar" ...>` row in
10
+ `<acquisition-plan>`, take the branch below that matches the row's `mode`
11
+ attribute. Calendar rows do not fan out per account today — the dispatcher
12
+ emits one row per active provider, scoped to the bound primary calendar.
13
+
14
+ Outlook Calendar is a **user-managed** integration: the daemon has no
15
+ delegation proxy (no `/api/integrations/outlook_calendar/exec` exists).
16
+ The four non-disabled branches therefore split into two real flows:
17
+
18
+ - `direct` → the daemon's Outlook calendar route
19
+ (`/api/calendar/outlook/events`).
20
+ - `delegated-same`, `delegated-cross`, `native` → use the in-session
21
+ connector surface your skills document. The user picks the binding (an
22
+ in-session connector, a local CLI invoked via a skill, a custom
23
+ script); the partial states the intent, not specific tool names. If no
24
+ surface is bound, record an error and continue.
25
+
26
+ Note on coverage: when `ContextBuilder.buildCalendarBlock` already covers
27
+ the window via `<calendar_events_*>` (multi-provider after §6.6), the
28
+ catalog skips the pre-pass row to avoid double-fetching. The pre-pass
29
+ only ships drift / retrospective / imminent windows.
30
+
31
+ POST every returned event to `http://localhost:8321/api/observations`:
32
+
33
+ - `source` = `"outlook_calendar:<calendarId>"` (use `"primary"` when
34
+ the provider returns no explicit id)
35
+ - `ref` = provider-side stable event id
36
+ - `changeType` = `"created"` for fresh events; `"modified"` when the
37
+ payload updates an existing `(source, ref)`; `"deleted"` for cancelled
38
+ events
39
+ - `actor` = `"agent"`
40
+ - `payload` = `{ "kind": "calendar", "providerId": "<calendarId>",
41
+ "raw": { "title": ..., "start": ..., "end": ...,
42
+ "attendees": [...], "status": ... } }`
43
+
44
+ Server computes the dedup hash from `(source, payload)`. Response shape:
45
+
46
+ - `200 {action: "created"|"modified"}` — fresh / updated row; count in `posted`.
47
+ - `409 {error: "duplicate"}` — identical payload already pending; count in `duplicates`.
48
+ - `409 {error: "integration_flip_in_progress"}` — append
49
+ `{type:"flip-locked","integration":"outlook_calendar"}` to `errors`
50
+ and move on.
51
+
52
+ <!-- mode:direct:outlook_calendar -->
53
+ GET `http://localhost:8321/api/calendar/outlook/events<query>` where
54
+ `<query>` is the literal `query` attribute of the `<fetch>` row (e.g.
55
+ `?date=2026-05-11&days=1` or `?date=2026-05-04&days=7`). The route
56
+ accepts `date=YYYY-MM-DD` (or `today`) plus `days=N` (≤90); `timeMin`
57
+ / `timeMax` are NOT recognised. The daemon returns
58
+ `{ "events": [...] }`; map each event into one observation POST as
59
+ specified above.
60
+ <!-- /mode:direct:outlook_calendar -->
61
+
62
+ <!-- mode:delegated-same:outlook_calendar -->
63
+ The integration is bound to your own session backend. Use the in-session
64
+ connector surface your skills document for Outlook Calendar; the
65
+ `<fetch>` row's `query` attribute carries the catalog's `delegated` form
66
+ (e.g. `startDateTime=... endDateTime=...`). Translate it into the args
67
+ your bound surface accepts. POST every returned event as specified above.
68
+
69
+ If no Outlook Calendar surface is bound, append
70
+ `{"type":"no-surface","integration":"outlook_calendar"}` to your `errors`
71
+ array and continue with the next row. Do NOT halt the pre-pass.
72
+ <!-- /mode:delegated-same:outlook_calendar -->
73
+
74
+ <!-- mode:delegated-cross:outlook_calendar -->
75
+ Outlook Calendar is user-managed, so the daemon does not host a
76
+ delegation proxy. The dispatcher should not have emitted a
77
+ `delegated-cross` row for this integration — if you see one, treat it
78
+ exactly like `delegated-same`: use whichever in-session connector
79
+ surface your skills document for Outlook Calendar. If nothing is bound,
80
+ append `{"type":"no-surface","integration":"outlook_calendar"}` to
81
+ `errors` and continue.
82
+ <!-- /mode:delegated-cross:outlook_calendar -->
83
+
84
+ <!-- mode:native:outlook_calendar -->
85
+ The integration is bound natively to your own session backend. Use the
86
+ in-session connector surface your skills document — same call shape as
87
+ `delegated-same`. The daemon does not proxy. POST every returned event as
88
+ specified above.
89
+
90
+ If no Outlook Calendar surface is bound, append
91
+ `{"type":"no-surface","integration":"outlook_calendar"}` to `errors` and
92
+ continue.
93
+ <!-- /mode:native:outlook_calendar -->
94
+
95
+ <!-- mode:disabled:outlook_calendar -->
96
+ Defensive no-op. The dispatcher filters disabled integrations out of
97
+ `<acquisition-plan>`. If a `<fetch integration="outlook_calendar">` row
98
+ still reaches this branch, skip it and append
99
+ `{"type":"unexpected-row","integration":"outlook_calendar","reason":"disabled-row-emitted"}`
100
+ to your `errors` array.
101
+ <!-- /mode:disabled:outlook_calendar -->
@@ -0,0 +1,113 @@
1
+ ---
2
+ name: mail-acquire.gmail
3
+ description: Acquire a Gmail message window per <acquisition-plan> row.
4
+ spec: ROUTINE_DATA_ACQUISITION_DESIGN.md §6.8 / §8.1
5
+ ---
6
+
7
+ # Gmail acquisition
8
+
9
+ For every `<fetch integration="gmail" ...>` row in `<acquisition-plan>`, take
10
+ the branch below that matches the row's `mode` attribute and acquire the
11
+ window described by `query`. Each row carries `account="<accountId>"` — apply
12
+ the query against that account only, do not pool across accounts.
13
+
14
+ POST every returned message to `http://localhost:8321/api/observations` per
15
+ the contract in your agent profile (the response from the upstream call IS
16
+ the payload; do not summarise or rank). Use:
17
+
18
+ - `source` = `"gmail:<accountId>"`
19
+ - `ref` = provider-side stable message id
20
+ - `changeType` = `"created"` for fresh items; `"modified"` when the row updates
21
+ a payload the server already has under the same `(source, ref)`
22
+ - `actor` = `"agent"`
23
+ - `payload` = `{ "kind": "mail", "providerId": "<accountId>", "raw": {
24
+ "subject": ..., "from": ..., "snippet": ...,
25
+ "date": ... } }`
26
+
27
+ Do NOT compute the dedup hash — the server derives it from `(source, payload)`.
28
+ The response body shape distinguishes three outcomes:
29
+
30
+ - `200 {action: "created"}` — fresh row inserted; count it in `posted`.
31
+ - `200 {action: "modified"}` — same `(source, ref)` pending row existed
32
+ with a different payload; the row was updated and re-summarized.
33
+ Count it in `posted`.
34
+ - `409 {error: "duplicate"}` — same `(source, ref)` pending row already
35
+ stores an identical payload. Nothing was written; count it in
36
+ `duplicates` and move on.
37
+ - `409 {error: "integration_flip_in_progress"}` — a mode flip is
38
+ draining for this integration. Record
39
+ `{type:"flip-locked","integration":"gmail","account":"<accountId>"}`
40
+ in `errors` and continue with the next row (the parent routine will
41
+ retry on the next tick).
42
+
43
+ <!-- mode:direct:gmail -->
44
+ GET `http://localhost:8321/api/mail/<accountId>/messages<query>` where
45
+ `<query>` is the literal `query` attribute of the `<fetch>` row (e.g.
46
+ `?since=2026-05-11T00:00:00.000Z&limit=20` or
47
+ `?since=2026-05-11T10:00:00.000Z&unreadOnly=true&limit=10` or
48
+ `?folder=sent&since=2026-05-11T00:00:00.000Z&limit=30`). The route
49
+ accepts `since` (ISO 8601 datetime), `limit`, `folder`, `q`,
50
+ `unreadOnly` — `days=…` is NOT recognised. The daemon returns
51
+ `{ "messages": [...] }`; map each item to one POST as specified above.
52
+ <!-- /mode:direct:gmail -->
53
+
54
+ <!-- mode:delegated-same:gmail -->
55
+ The Gmail connector is bound to your own session backend. Use the in-session
56
+ connector surface your skills document for this integration; the `<fetch>`
57
+ row's `query` attribute carries the catalog's `delegated` form (e.g.
58
+ `q="newer_than:1d" maxResults=20`) — translate it into the args your bound
59
+ surface accepts. POST every returned message as specified above. The daemon
60
+ does not proxy in this branch.
61
+ <!-- /mode:delegated-same:gmail -->
62
+
63
+ <!-- mode:delegated-cross:gmail -->
64
+ The connector is bound to a different backend than this session, so reach it
65
+ through the daemon's delegation proxy. POST to
66
+ `http://localhost:8321/api/integrations/gmail/exec` with the following body
67
+ (substitute the row's `query` and `accountId` into `task`):
68
+
69
+ ```json
70
+ {
71
+ "task": "On account <accountId>, search Gmail with the query expression <query> and return id, subject, from, snippet, date for each message. Up to 30 messages.",
72
+ "outputSchema": {
73
+ "type": "object",
74
+ "required": ["messages"],
75
+ "properties": {
76
+ "messages": {
77
+ "type": "array",
78
+ "items": {
79
+ "type": "object",
80
+ "required": ["id"],
81
+ "properties": {
82
+ "id": { "type": "string" },
83
+ "subject": { "type": "string" },
84
+ "from": { "type": "string" },
85
+ "snippet": { "type": "string" },
86
+ "date": { "type": "string" }
87
+ }
88
+ }
89
+ }
90
+ }
91
+ },
92
+ "maxToolCalls": 3,
93
+ "cacheable": true
94
+ }
95
+ ```
96
+
97
+ Map each item in `result.messages[]` to one observation POST.
98
+ <!-- /mode:delegated-cross:gmail -->
99
+
100
+ <!-- mode:native:gmail -->
101
+ The connector is bound natively to your own session backend. Use the
102
+ in-session connector surface your skills document — same call shape as
103
+ `delegated-same`. The daemon does not proxy. POST every returned message as
104
+ specified above.
105
+ <!-- /mode:native:gmail -->
106
+
107
+ <!-- mode:disabled:gmail -->
108
+ Defensive no-op. The dispatcher filters disabled integrations out of
109
+ `<acquisition-plan>`, so no `<fetch integration="gmail">` row should ever
110
+ land in this branch. If one does, skip it and append
111
+ `{"type":"unexpected-row","integration":"gmail","reason":"disabled-row-emitted"}`
112
+ to your `errors` array.
113
+ <!-- /mode:disabled:gmail -->
@@ -0,0 +1,97 @@
1
+ ---
2
+ name: mail-acquire.outlook_mail
3
+ description: Acquire an Outlook Mail message window per <acquisition-plan> row.
4
+ spec: ROUTINE_DATA_ACQUISITION_DESIGN.md §6.8 / §8.2
5
+ ---
6
+
7
+ # Outlook Mail acquisition
8
+
9
+ For every `<fetch integration="outlook_mail" ...>` row in `<acquisition-plan>`,
10
+ take the branch below that matches the row's `mode` attribute. Each row
11
+ carries `account="<accountId>"` — apply the query to that account only.
12
+
13
+ Outlook Mail is a **user-managed** integration: the daemon has no
14
+ delegation proxy for it (no `/api/integrations/outlook_mail/exec` exists).
15
+ The four non-disabled branches therefore split into two real flows:
16
+
17
+ - `direct` → the unified daemon mail route (transparently handles Outlook
18
+ accounts).
19
+ - `delegated-same`, `delegated-cross`, `native` → use the in-session
20
+ connector surface your skills document. The user picks the binding (an
21
+ in-session connector, a local CLI invoked via a skill, a custom script);
22
+ this partial states the intent, not specific tool names. If no surface
23
+ is bound, record an error and continue.
24
+
25
+ POST every returned message to `http://localhost:8321/api/observations`:
26
+
27
+ - `source` = `"outlook_mail:<accountId>"`
28
+ - `ref` = provider-side stable message id
29
+ - `changeType` = `"created"` for fresh items; `"modified"` when the row
30
+ updates a payload already known under `(source, ref)`
31
+ - `actor` = `"agent"`
32
+ - `payload` = `{ "kind": "mail", "providerId": "<accountId>", "raw": {
33
+ "subject": ..., "from": ..., "snippet": ...,
34
+ "date": ... } }`
35
+
36
+ The server computes the dedup hash from `(source, payload)`. Response
37
+ shape:
38
+
39
+ - `200 {action: "created"|"modified"}` — fresh / updated row; count in `posted`.
40
+ - `409 {error: "duplicate"}` — identical payload already pending; count in `duplicates`.
41
+ - `409 {error: "integration_flip_in_progress"}` — append
42
+ `{type:"flip-locked","integration":"outlook_mail","account":"<accountId>"}`
43
+ to `errors` and move on; do not retry inline.
44
+
45
+ <!-- mode:direct:outlook_mail -->
46
+ GET `http://localhost:8321/api/mail/<accountId>/messages<query>` where
47
+ `<query>` is the literal `query` attribute of the `<fetch>` row (e.g.
48
+ `?since=2026-05-11T00:00:00.000Z&limit=20` or
49
+ `?folder=sent&since=2026-05-11T00:00:00.000Z&limit=30`). The route
50
+ accepts `since` (ISO 8601), `limit`, `folder`, `q`, `unreadOnly` — it
51
+ does NOT accept `days=…`. The daemon returns `{ "messages": [...] }`
52
+ regardless of the underlying provider; map each item into one
53
+ observation POST as specified above.
54
+ <!-- /mode:direct:outlook_mail -->
55
+
56
+ <!-- mode:delegated-same:outlook_mail -->
57
+ The integration is bound to your own session backend. Use the in-session
58
+ connector surface your skills document for Outlook Mail. The `<fetch>`
59
+ row's `query` attribute carries the catalog's `delegated` form (e.g.
60
+ `filter=receivedDateTime ge 2026-05-11T00:00:00Z`) — translate it into the
61
+ args your bound surface accepts. POST every returned message as specified
62
+ above.
63
+
64
+ If no Outlook Mail surface is bound on this backend, append
65
+ `{"type":"no-surface","integration":"outlook_mail","account":"<accountId>"}`
66
+ to your `errors` array and continue with the next row. Do NOT halt the
67
+ pre-pass; the parent routine continues with whatever observations the rest
68
+ of the plan produced.
69
+ <!-- /mode:delegated-same:outlook_mail -->
70
+
71
+ <!-- mode:delegated-cross:outlook_mail -->
72
+ Outlook Mail is user-managed, so the daemon does not host a delegation
73
+ proxy. The dispatcher should not have emitted a `delegated-cross` row for
74
+ this integration — if you see one, treat it exactly like
75
+ `delegated-same`: use whichever in-session surface your skills document
76
+ for Outlook Mail. If nothing is bound, append
77
+ `{"type":"no-surface","integration":"outlook_mail","account":"<accountId>"}`
78
+ to `errors` and continue.
79
+ <!-- /mode:delegated-cross:outlook_mail -->
80
+
81
+ <!-- mode:native:outlook_mail -->
82
+ The integration is bound natively to your own session backend. Use the
83
+ in-session connector surface your skills document for Outlook Mail —
84
+ same call shape as `delegated-same`. The daemon does not proxy.
85
+
86
+ If no Outlook Mail surface is bound, append
87
+ `{"type":"no-surface","integration":"outlook_mail","account":"<accountId>"}`
88
+ to `errors` and continue.
89
+ <!-- /mode:native:outlook_mail -->
90
+
91
+ <!-- mode:disabled:outlook_mail -->
92
+ Defensive no-op. The dispatcher filters disabled integrations out of
93
+ `<acquisition-plan>`. If a `<fetch integration="outlook_mail">` row still
94
+ reaches this branch, skip it and append
95
+ `{"type":"unexpected-row","integration":"outlook_mail","reason":"disabled-row-emitted"}`
96
+ to your `errors` array.
97
+ <!-- /mode:disabled:outlook_mail -->
@@ -0,0 +1,104 @@
1
+ ---
2
+ name: notion-acquire.notion
3
+ description: Acquire recently-updated Notion pages per <acquisition-plan> row.
4
+ spec: ROUTINE_DATA_ACQUISITION_DESIGN.md §6.8 / §8.5
5
+ ---
6
+
7
+ # Notion acquisition
8
+
9
+ For every `<fetch integration="notion" ...>` row in `<acquisition-plan>`,
10
+ take the branch below that matches the row's `mode` attribute. Notion rows
11
+ do not fan out per account — the dispatcher emits one row per workspace.
12
+
13
+ POST every returned page to `http://localhost:8321/api/observations`:
14
+
15
+ - `source` = `"notion:<workspaceId>"` (use `"default"` when the daemon
16
+ reports no explicit workspace id)
17
+ - `ref` = Notion page id (stable UUID)
18
+ - `changeType` = `"created"` for fresh pages; `"modified"` when the
19
+ payload updates an existing `(source, ref)`
20
+ - `actor` = `"agent"`
21
+ - `payload` = `{ "kind": "notion", "providerId": "<workspaceId>",
22
+ "raw": { "title": ..., "last_edited": ...,
23
+ "parent": ..., "url": ... } }`
24
+
25
+ The server computes the dedup hash from `(source, payload)`. Response shape:
26
+
27
+ - `200 {action: "created"|"modified"}` — fresh / updated row; count in `posted`.
28
+ - `409 {error: "duplicate"}` — identical payload already pending; count in `duplicates`.
29
+ - `409 {error: "integration_flip_in_progress"}` — append
30
+ `{type:"flip-locked","integration":"notion"}` to `errors` and move on.
31
+
32
+ <!-- mode:direct:notion -->
33
+ GET `http://localhost:8321/api/notion/search<query>` where `<query>` is
34
+ the literal `query` attribute of the `<fetch>` row (e.g.
35
+ `?page_size=50&sort=descending` or `?page_size=20&sort=descending`).
36
+ The route accepts `q`, `type`, `sort` (`ascending` / `descending`),
37
+ `page_size` (≤100), `start_cursor` — it has NO time filter, so do the
38
+ window cutoff client-side. The daemon returns `{ "results": [...] }`
39
+ sorted by `last_edited_time` descending; filter to entries whose
40
+ `last_edited_time` is at or after the window the `<fetch>` row's
41
+ window symbol implies (`updated_24h` → today's agent-day start,
42
+ `updated_1h` → the current hour boundary), then map each surviving
43
+ page into one observation POST as specified above.
44
+ <!-- /mode:direct:notion -->
45
+
46
+ <!-- mode:delegated-same:notion -->
47
+ The connector is bound to your own session backend. Use the in-session
48
+ connector surface your skills document for Notion; the `<fetch>` row's
49
+ `query` attribute carries the catalog's `delegated` form
50
+ (e.g. `last_edited_time>=<iso>`). Translate it into the args your bound
51
+ surface accepts. POST every returned page as specified above.
52
+ <!-- /mode:delegated-same:notion -->
53
+
54
+ <!-- mode:delegated-cross:notion -->
55
+ The connector is bound to a different backend than this session — reach
56
+ it through the daemon's delegation proxy. POST to
57
+ `http://localhost:8321/api/integrations/notion/exec` with the following
58
+ body (substitute the row's `query` into `task`):
59
+
60
+ ```json
61
+ {
62
+ "task": "Search Notion for pages with last_edited_time matching <query>. Return id, title, last_edited, parent, url for each page. Up to 50 pages.",
63
+ "outputSchema": {
64
+ "type": "object",
65
+ "required": ["pages"],
66
+ "properties": {
67
+ "pages": {
68
+ "type": "array",
69
+ "items": {
70
+ "type": "object",
71
+ "required": ["id"],
72
+ "properties": {
73
+ "id": { "type": "string" },
74
+ "title": { "type": "string" },
75
+ "last_edited": { "type": "string" },
76
+ "parent": { "type": "string" },
77
+ "url": { "type": "string" }
78
+ }
79
+ }
80
+ }
81
+ }
82
+ },
83
+ "maxToolCalls": 3,
84
+ "cacheable": true
85
+ }
86
+ ```
87
+
88
+ Map each item in `result.pages[]` to one observation POST.
89
+ <!-- /mode:delegated-cross:notion -->
90
+
91
+ <!-- mode:native:notion -->
92
+ The connector is bound natively to your own session backend. Use the
93
+ in-session connector surface your skills document — same call shape as
94
+ `delegated-same`. The daemon does not proxy. POST every returned page as
95
+ specified above.
96
+ <!-- /mode:native:notion -->
97
+
98
+ <!-- mode:disabled:notion -->
99
+ Defensive no-op. The dispatcher filters disabled integrations out of
100
+ `<acquisition-plan>`. If a `<fetch integration="notion">` row still
101
+ reaches this branch, skip it and append
102
+ `{"type":"unexpected-row","integration":"notion","reason":"disabled-row-emitted"}`
103
+ to your `errors` array.
104
+ <!-- /mode:disabled:notion -->
@@ -10,6 +10,28 @@ surgical merge — you submit only the new section body and it replaces the
10
10
  marker-bracketed Architecture block in place. Other sections (Summary,
11
11
  Notable Changes, Daily Activity Log) are preserved automatically.
12
12
 
13
+ ## Tools available to you
14
+
15
+ This session runs under a **read-only clamp**. Your only write surface is the
16
+ single daemon-API call in Step 6. The runtime denies every other writer.
17
+
18
+ Available:
19
+
20
+ - `Read` / `Glob` / `Grep` — inspect the repository at `<localPath>` and any
21
+ documentation under it. Pass absolute paths; you do **not** need to `cd`.
22
+ - `Bash(curl *)` — pinned to `localhost:<apiPort>` by the security hook; used
23
+ exclusively for the Step 6 `PUT`.
24
+ - `Bash(jq *)` — JSON post-processing for the curl response.
25
+
26
+ Denied (these tools will fail at the SDK layer — do not attempt them):
27
+
28
+ - `Write`, `Edit` — no agent-side write to the worktree. The daemon owns
29
+ every byte under `git/<slug>/`.
30
+ - `Bash(git ...)`, `Bash(ls ...)`, `Bash(cat ...)` and any other shell verbs —
31
+ use `Glob` to enumerate directories and `Read` to inspect files. You are
32
+ analysing *current code structure*, not git history, so the git CLI is not
33
+ needed here. The daily-journal cron is the path that consumes git history.
34
+
13
35
  ## Inputs
14
36
 
15
37
  Read `<task_context>` first. It contains:
@@ -52,7 +74,8 @@ synthesize.
52
74
  1. **Read the README** at `<localPath>/README.md` (or `README.*`). Use
53
75
  it as the author's stated framing of the project, but verify against
54
76
  the code; the README can drift.
55
- 2. **Survey top-level structure.** `ls <localPath>` + targeted reads of
77
+ 2. **Survey top-level structure.** `Glob` `<localPath>/*` (and
78
+ `<localPath>/.*` for dotfiles you care about) + targeted `Read`s of
56
79
  `package.json` / `pyproject.toml` / `Cargo.toml` / `go.mod` / etc. to
57
80
  identify the language, build system, and entry points.
58
81
  3. **Walk the meaningful directories.** Read enough source to confirm
@@ -75,6 +75,9 @@ same-backend delegated → your session's native Google Calendar MCP tools (no s
75
75
  <!-- mode:delegated-cross:google_calendar -->
76
76
  cross-backend delegated → `POST /api/integrations/google_calendar/exec` with a natural-language `task` + `outputSchema`, via the external-services skill (cross-backend variant materialized for this session). Do NOT call `/api/calendar/events` (returns 410) and do NOT fall back to your own backend's native Calendar MCP tools — they read a different Google account.
77
77
  <!-- /mode:delegated-cross:google_calendar -->
78
+ <!-- mode:native:google_calendar -->
79
+ native → your session backend's native Google Calendar MCP tools (see the `external-services` skill's native body for the exact tool namespace per backend). `/api/calendar/*` returns 410 in native mode and `POST /api/integrations/google_calendar/exec` returns 410 with `X-Integration-Mode: native` — do NOT call either; the daemon does not proxy Calendar in native mode.
80
+ <!-- /mode:native:google_calendar -->
78
81
  <!-- mode:disabled:google_calendar -->
79
82
  disabled → tell the user real-time calendar access is unavailable in this configuration; work from the morning snapshot in <today> only.
80
83
  <!-- /mode:disabled:google_calendar -->
@@ -0,0 +1,76 @@
1
+ {context}
2
+
3
+ ## DM Reply — Native Mode (Claude connectors)
4
+
5
+ Variant of `message.received.dm` selected when at least one integration
6
+ the agent may need to reach during this DM is in `native` mode and the
7
+ session backend (Claude) matches the integration's `nativeBackend`.
8
+
9
+ > **Connector routing (native).** `<integration_modes>` carries
10
+ > `gmail`, `google_calendar`, and `notion` mode attributes plus
11
+ > `<key>_native_backend="<backend>"` for every native key. The
12
+ > `<integration-routing-table>` block below is the audit table; the
13
+ > actionable per-integration routing table is what you act on. For any
14
+ > native integration the user's DM may need:
15
+ >
16
+ > - Call `mcp__claude_ai_<Service>__*` MCP tools directly. The exact
17
+ > tool namespace per service is documented in each skill's
18
+ > `SKILL.native.claude.md` body materialised for this session
19
+ > (`mail`, `external-services`, `notion`).
20
+ > - Do **NOT** call `POST /api/integrations/<key>/exec` — returns 410
21
+ > with `X-Integration-Mode: native`.
22
+ > - Do **NOT** call the per-integration daemon routes listed in
23
+ > `<integration-routing-table>` as `(DO NOT call /api/<key>/*)` —
24
+ > each route-prefix returns 410 in native mode.
25
+ > - Write-class MCP calls (per each connector's registry
26
+ > `destructiveTools` set) still require explicit user confirmation
27
+ > per `docs/design/09-safety-cost.md` §7. The absolute-block layer
28
+ > continues to fire regardless of mode.
29
+ > - For direct-mode siblings (e.g. when `gmail="native"` and
30
+ > `google_calendar="direct"`), keep using the direct routes
31
+ > documented in the base skill body for the direct integrations —
32
+ > the native gate is per-key.
33
+
34
+ ### Per-session integration routing
35
+
36
+ <integration-routing-table>
37
+
38
+ ### Native MCP routing — per integration
39
+
40
+ The Calendar / Mail / Notion entries below override the matching
41
+ sections in the shared base flow. Mode-conditional markers in the base
42
+ (`<!-- mode:native:google_calendar -->`) still fire, but the explicit
43
+ prose here carries the exact tool namespace and the safety contract
44
+ for Claude's hosted connectors so the agent does not have to derive it
45
+ mid-turn.
46
+
47
+ **Calendar — real-time queries.** When `google_calendar="native"`, use
48
+ the `mcp__claude_ai_Google_Calendar__*` MCP tools (`list_events` /
49
+ `get_event` / `list_calendars` / `suggest_time`). See the
50
+ `external-services` skill's native body for argument shapes and the
51
+ destructive-confirm contract for `create_event` / `update_event` /
52
+ `delete_event` / `respond_to_event`. `/api/calendar/*` returns 410.
53
+ `POST /api/integrations/google_calendar/exec` returns 410.
54
+
55
+ **Mail — DM read / draft / reply.** When `gmail="native"`, use the
56
+ `mcp__claude_ai_Gmail__*` MCP tools per the `mail` skill's native
57
+ body (`search_threads` / `get_thread` / `create_draft` / label
58
+ operations). Claude's hosted Gmail connector is **draft-only** —
59
+ there is no send / forward / delete surface. If the user explicitly
60
+ asks to send, point them at the Gmail web UI. For non-Gmail accounts
61
+ (IMAP / Outlook / iCloud / Yahoo), keep using the direct-mode
62
+ `/api/mail/<acct>/*` routes per the base `mail` skill body.
63
+
64
+ **Notion — DM read / search / page operations.** When `notion="native"`,
65
+ use the `mcp__claude_ai_Notion__*` MCP tools per the `notion` skill's
66
+ native body (`notion-search` / `notion-fetch` / read-class + the
67
+ destructive set). `/api/notion/databases` (label → UUID config dump)
68
+ is still reachable in every mode — consult it before any Notion
69
+ read so the MCP call carries a concrete UUID.
70
+
71
+ The dispatch decision flow (capture user info, profile-question
72
+ reconcile, compose reply, route durable intent) is identical to the
73
+ direct variant and lives below via the `{{> base }}` partial. Read
74
+ through it after the per-integration overrides above.
75
+
76
+ {{> base }}