typeclaw 0.1.6 → 0.3.0
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/package.json +1 -1
- package/src/agent/index.ts +24 -3
- package/src/agent/system-prompt.ts +17 -0
- package/src/agent/tools/channel-send.ts +2 -3
- package/src/bundled-plugins/memory/README.md +8 -8
- package/src/bundled-plugins/memory/append-tool.ts +10 -7
- package/src/bundled-plugins/memory/citations.ts +45 -0
- package/src/bundled-plugins/memory/dreaming-state.ts +30 -18
- package/src/bundled-plugins/memory/dreaming.ts +179 -48
- package/src/bundled-plugins/memory/load-memory.ts +15 -9
- package/src/bundled-plugins/memory/migration.ts +9 -8
- package/src/bundled-plugins/memory/stream-events.ts +30 -0
- package/src/channels/adapters/kakaotalk.ts +7 -6
- package/src/cli/model.ts +51 -19
- package/src/cli/provider.ts +38 -24
- package/src/config/models-mutation.ts +34 -6
- package/src/init/index.ts +8 -0
- package/src/run/channel-session-factory.ts +2 -0
- package/src/run/index.ts +6 -0
- package/src/server/index.ts +3 -0
- package/src/skills/typeclaw-memory/SKILL.md +15 -15
package/src/cli/provider.ts
CHANGED
|
@@ -48,37 +48,51 @@ const addSub = defineCommand({
|
|
|
48
48
|
},
|
|
49
49
|
async run({ args }) {
|
|
50
50
|
const cwd = ensureAgentDir()
|
|
51
|
-
const
|
|
52
|
-
const provider = KNOWN_PROVIDERS[providerId]
|
|
53
|
-
|
|
54
|
-
intro(`Adding provider: ${provider.name}`)
|
|
55
|
-
|
|
56
|
-
const method = await resolveAuthMethod(provider, args)
|
|
57
|
-
if (method === 'oauth') {
|
|
58
|
-
const result = await runOAuthLogin(cwd, providerId)
|
|
59
|
-
if (!result.ok) {
|
|
60
|
-
console.error(errorLine(`OAuth login failed: ${result.reason}`))
|
|
61
|
-
process.exit(1)
|
|
62
|
-
}
|
|
63
|
-
done({
|
|
64
|
-
title: c.green(`Logged in to ${provider.name}.`),
|
|
65
|
-
hints: nextStepHints({ credentialChanged: true }),
|
|
66
|
-
})
|
|
67
|
-
return
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const credential = await resolveApiKeyInputs(provider, args)
|
|
71
|
-
const result = addProvider(cwd, providerId, credential)
|
|
51
|
+
const result = await runProviderAddFlow(cwd, args)
|
|
72
52
|
if (!result.ok) {
|
|
73
53
|
console.error(errorLine(result.reason))
|
|
74
54
|
process.exit(1)
|
|
75
55
|
}
|
|
56
|
+
},
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
export type ProviderAddFlowArgs = {
|
|
60
|
+
provider?: string | undefined
|
|
61
|
+
key?: string | undefined
|
|
62
|
+
env?: string | undefined
|
|
63
|
+
oauth?: boolean | undefined
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export type ProviderAddFlowResult =
|
|
67
|
+
| { ok: true; providerId: KnownProviderId; method: 'api-key' | 'oauth' }
|
|
68
|
+
| { ok: false; reason: string }
|
|
69
|
+
|
|
70
|
+
export async function runProviderAddFlow(cwd: string, args: ProviderAddFlowArgs): Promise<ProviderAddFlowResult> {
|
|
71
|
+
const providerId = await resolveProviderForAdd(args.provider)
|
|
72
|
+
const provider = KNOWN_PROVIDERS[providerId]
|
|
73
|
+
|
|
74
|
+
intro(`Adding provider: ${provider.name}`)
|
|
75
|
+
|
|
76
|
+
const method = await resolveAuthMethod(provider, args)
|
|
77
|
+
if (method === 'oauth') {
|
|
78
|
+
const result = await runOAuthLogin(cwd, providerId)
|
|
79
|
+
if (!result.ok) return { ok: false, reason: `OAuth login failed: ${result.reason}` }
|
|
76
80
|
done({
|
|
77
|
-
title: c.green(`
|
|
81
|
+
title: c.green(`Logged in to ${provider.name}.`),
|
|
78
82
|
hints: nextStepHints({ credentialChanged: true }),
|
|
79
83
|
})
|
|
80
|
-
|
|
81
|
-
}
|
|
84
|
+
return { ok: true, providerId, method: 'oauth' }
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const credential = await resolveApiKeyInputs(provider, args)
|
|
88
|
+
const result = addProvider(cwd, providerId, credential)
|
|
89
|
+
if (!result.ok) return { ok: false, reason: result.reason }
|
|
90
|
+
done({
|
|
91
|
+
title: c.green(`Added ${provider.name} credentials to secrets.json.`),
|
|
92
|
+
hints: nextStepHints({ credentialChanged: true }),
|
|
93
|
+
})
|
|
94
|
+
return { ok: true, providerId, method: 'api-key' }
|
|
95
|
+
}
|
|
82
96
|
|
|
83
97
|
const setSub = defineCommand({
|
|
84
98
|
meta: {
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { readFileSync, writeFileSync } from 'node:fs'
|
|
2
2
|
import { join } from 'node:path'
|
|
3
3
|
|
|
4
|
+
import { commitSystemFileSync } from '@/git/system-commit'
|
|
5
|
+
|
|
4
6
|
import { configSchema, loadConfigSync, validateConfig } from './config'
|
|
5
7
|
import {
|
|
6
8
|
KNOWN_PROVIDERS,
|
|
@@ -9,7 +11,7 @@ import {
|
|
|
9
11
|
type KnownModelRef,
|
|
10
12
|
type KnownProviderId,
|
|
11
13
|
} from './providers'
|
|
12
|
-
import { isProviderConfigured } from './providers-mutation'
|
|
14
|
+
import { isProviderConfigured, listConfiguredProviders } from './providers-mutation'
|
|
13
15
|
|
|
14
16
|
const CONFIG_FILE = 'typeclaw.json'
|
|
15
17
|
|
|
@@ -49,6 +51,25 @@ export function listAvailableModelRefs(): KnownModelRef[] {
|
|
|
49
51
|
return listKnownModelRefs()
|
|
50
52
|
}
|
|
51
53
|
|
|
54
|
+
// Subset of `listAvailableModelRefs()` filtered to providers with a usable
|
|
55
|
+
// credential in this agent folder — either a `secrets.json#providers.<id>`
|
|
56
|
+
// entry (api-key OR oauth) or a credential resolvable from the process env
|
|
57
|
+
// via the provider's canonical env-var name. Used by `typeclaw model set`'s
|
|
58
|
+
// interactive picker so users only see models they can actually run; the
|
|
59
|
+
// CLI surfaces an explicit "add provider" sentinel when the result is empty
|
|
60
|
+
// or when the user wants to wire a new one.
|
|
61
|
+
//
|
|
62
|
+
// Ordering preserves `listKnownModelRefs()` (provider-table declaration
|
|
63
|
+
// order, then per-provider model order) so the picker reads stably across
|
|
64
|
+
// invocations.
|
|
65
|
+
export function listRegisteredModelRefs(cwd: string, env: NodeJS.ProcessEnv = process.env): KnownModelRef[] {
|
|
66
|
+
const registered = new Set<KnownProviderId>()
|
|
67
|
+
for (const entry of listConfiguredProviders(cwd, env)) {
|
|
68
|
+
if (entry.known) registered.add(entry.id as KnownProviderId)
|
|
69
|
+
}
|
|
70
|
+
return listKnownModelRefs().filter((ref) => registered.has(providerForModelRef(ref)))
|
|
71
|
+
}
|
|
72
|
+
|
|
52
73
|
export function isKnownModelRef(value: string): value is KnownModelRef {
|
|
53
74
|
return (listKnownModelRefs() as ReadonlyArray<string>).includes(value)
|
|
54
75
|
}
|
|
@@ -87,7 +108,9 @@ export function setProfile(
|
|
|
87
108
|
}
|
|
88
109
|
}
|
|
89
110
|
|
|
90
|
-
|
|
111
|
+
const existingBefore = readModelsRaw(cwd)
|
|
112
|
+
const verb = existingBefore !== null && trimmed in existingBefore ? 'set' : 'add'
|
|
113
|
+
return writeProfile(cwd, trimmed, ref, `model: ${verb} ${trimmed} → ${ref}`)
|
|
91
114
|
}
|
|
92
115
|
|
|
93
116
|
// `add` is just `set` with a uniqueness guard; users who want "update" should
|
|
@@ -130,19 +153,19 @@ export function removeProfile(cwd: string, profile: string): ModelMutationResult
|
|
|
130
153
|
}
|
|
131
154
|
const next = { ...existing }
|
|
132
155
|
delete next[profile]
|
|
133
|
-
return writeModels(cwd, next)
|
|
156
|
+
return writeModels(cwd, next, `model: remove ${profile}`)
|
|
134
157
|
}
|
|
135
158
|
|
|
136
|
-
function writeProfile(cwd: string, profile: string, ref: KnownModelRef): ModelMutationResult {
|
|
159
|
+
function writeProfile(cwd: string, profile: string, ref: KnownModelRef, message: string): ModelMutationResult {
|
|
137
160
|
const existing = readModelsRaw(cwd)
|
|
138
161
|
const next = existing === null ? { default: ref } : { ...existing, [profile]: ref }
|
|
139
162
|
if (existing === null && profile !== 'default') {
|
|
140
163
|
next.default = ref
|
|
141
164
|
}
|
|
142
|
-
return writeModels(cwd, next)
|
|
165
|
+
return writeModels(cwd, next, message)
|
|
143
166
|
}
|
|
144
167
|
|
|
145
|
-
function writeModels(cwd: string, models: Record<string, string
|
|
168
|
+
function writeModels(cwd: string, models: Record<string, string>, commitMessage: string): ModelMutationResult {
|
|
146
169
|
const path = join(cwd, CONFIG_FILE)
|
|
147
170
|
let parsed: Record<string, unknown>
|
|
148
171
|
try {
|
|
@@ -176,6 +199,11 @@ function writeModels(cwd: string, models: Record<string, string>): ModelMutation
|
|
|
176
199
|
if (!validation.ok) {
|
|
177
200
|
return { ok: false, reason: validation.reason }
|
|
178
201
|
}
|
|
202
|
+
// Auto-commit so the agent folder is never silently dirty after a CLI
|
|
203
|
+
// config mutation. Same pattern as `persistMigratedConfig` and cron
|
|
204
|
+
// migrations: `commitSystemFileSync` no-ops on non-git folders, missing
|
|
205
|
+
// Bun, and clean files, so callers outside a git repo pay zero cost.
|
|
206
|
+
commitSystemFileSync(cwd, CONFIG_FILE, commitMessage)
|
|
179
207
|
return { ok: true }
|
|
180
208
|
}
|
|
181
209
|
|
package/src/init/index.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
type KnownProviderId,
|
|
13
13
|
} from '@/config/providers'
|
|
14
14
|
import { checkDockerAvailable, type DockerAvailability, type DockerExec, start } from '@/container'
|
|
15
|
+
import { commitSystemFile } from '@/git/system-commit'
|
|
15
16
|
import { createSecretsStoreForAgent, type Channels, type Secret, SecretsBackend } from '@/secrets'
|
|
16
17
|
import { createTui } from '@/tui'
|
|
17
18
|
|
|
@@ -860,6 +861,13 @@ export async function runAddChannel(options: AddChannelOptions): Promise<void> {
|
|
|
860
861
|
await appendChannelSecrets(options.cwd, options.channel, tokens)
|
|
861
862
|
}
|
|
862
863
|
emit({ step: 'secrets', phase: 'done' })
|
|
864
|
+
|
|
865
|
+
// Commit the typeclaw.json change so the agent folder isn't silently
|
|
866
|
+
// dirty after `typeclaw channel add`. Same `commitSystemFile` contract as
|
|
867
|
+
// every other host-side rewrite: no-op outside a git repo, when Bun is
|
|
868
|
+
// unavailable, or when the file is clean. secrets.json is gitignored, so
|
|
869
|
+
// only typeclaw.json is named here.
|
|
870
|
+
await commitSystemFile(options.cwd, CONFIG_FILE, `channel: add ${options.channel}`)
|
|
863
871
|
}
|
|
864
872
|
|
|
865
873
|
function channelSecretsFromOptions(options: AddChannelOptions): ChannelSecrets {
|
|
@@ -33,6 +33,7 @@ export type BuildChannelSessionFactoryDeps = {
|
|
|
33
33
|
// their inbound messages came from.
|
|
34
34
|
getChannelRouter: () => ChannelRouter
|
|
35
35
|
containerName?: string
|
|
36
|
+
runtimeVersion?: string
|
|
36
37
|
// When set, rehydrating a session JSONL caps oversized tool results in the
|
|
37
38
|
// file before pi-coding-agent reads it. `null` disables the load-time pass
|
|
38
39
|
// (tool-result-cap.enabled=false in config, or no plugin block at all).
|
|
@@ -105,6 +106,7 @@ export function buildChannelSessionFactory(deps: BuildChannelSessionFactoryDeps)
|
|
|
105
106
|
}
|
|
106
107
|
: {}),
|
|
107
108
|
...(deps.containerName !== undefined ? { containerName: deps.containerName } : {}),
|
|
109
|
+
...(deps.runtimeVersion !== undefined ? { runtimeVersion: deps.runtimeVersion } : {}),
|
|
108
110
|
...(deps.permissions !== undefined ? { permissions: deps.permissions } : {}),
|
|
109
111
|
})
|
|
110
112
|
|
package/src/run/index.ts
CHANGED
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
loadCron as loadCronDefault,
|
|
25
25
|
type Scheduler,
|
|
26
26
|
} from '@/cron'
|
|
27
|
+
import { CLI_VERSION } from '@/init/cli-version'
|
|
27
28
|
import { loadPlugins, type LoadPluginsResult, pluginCronJobs, type PluginRegistry, summarizeLoaded } from '@/plugin'
|
|
28
29
|
import { createContainerBroker, publishForwardResult } from '@/portbroker'
|
|
29
30
|
import { ReloadRegistry } from '@/reload'
|
|
@@ -90,6 +91,7 @@ export async function startAgent({
|
|
|
90
91
|
// which is what we want, since there is no host daemon to honor it anyway.
|
|
91
92
|
const containerName = process.env.TYPECLAW_CONTAINER_NAME
|
|
92
93
|
const containerNameOpt = containerName !== undefined ? { containerName } : {}
|
|
94
|
+
const runtimeVersionOpt = { runtimeVersion: CLI_VERSION }
|
|
93
95
|
const tuiToken = process.env.TYPECLAW_TUI_TOKEN
|
|
94
96
|
const tuiTokenOpt = tuiToken !== undefined && tuiToken !== '' ? { tuiToken } : {}
|
|
95
97
|
|
|
@@ -155,6 +157,7 @@ export async function startAgent({
|
|
|
155
157
|
rehydrateCapOptions: resolveCapOptionsFromConfig(pluginConfigsByName['tool-result-cap']),
|
|
156
158
|
permissions: pluginsLoaded.permissions,
|
|
157
159
|
...containerNameOpt,
|
|
160
|
+
...runtimeVersionOpt,
|
|
158
161
|
}),
|
|
159
162
|
permissions: pluginsLoaded.permissions,
|
|
160
163
|
claimHandler: claimController.claimHandler,
|
|
@@ -198,6 +201,7 @@ export async function startAgent({
|
|
|
198
201
|
...(entry.pluginSubagent.toolResultBudget !== undefined
|
|
199
202
|
? { toolResultBudget: entry.pluginSubagent.toolResultBudget }
|
|
200
203
|
: {}),
|
|
204
|
+
...runtimeVersionOpt,
|
|
201
205
|
})
|
|
202
206
|
return {
|
|
203
207
|
...created,
|
|
@@ -267,6 +271,7 @@ export async function startAgent({
|
|
|
267
271
|
}
|
|
268
272
|
: {}),
|
|
269
273
|
...containerNameOpt,
|
|
274
|
+
...runtimeVersionOpt,
|
|
270
275
|
})
|
|
271
276
|
return {
|
|
272
277
|
prompt: (text) => session.prompt(text),
|
|
@@ -363,6 +368,7 @@ export async function startAgent({
|
|
|
363
368
|
pluginRuntime,
|
|
364
369
|
claimController,
|
|
365
370
|
...containerNameOpt,
|
|
371
|
+
...runtimeVersionOpt,
|
|
366
372
|
...tuiTokenOpt,
|
|
367
373
|
...containerBrokerOpt,
|
|
368
374
|
}).start()
|
package/src/server/index.ts
CHANGED
|
@@ -38,6 +38,7 @@ export type ServerOptions = {
|
|
|
38
38
|
agentDir?: string
|
|
39
39
|
pluginRuntime?: PluginRuntime
|
|
40
40
|
containerName?: string
|
|
41
|
+
runtimeVersion?: string
|
|
41
42
|
tuiToken?: string
|
|
42
43
|
// Optional in-process portbroker handler. When provided, requests to the
|
|
43
44
|
// /portbroker WS path are routed to it instead of being treated as TUI
|
|
@@ -108,6 +109,7 @@ export function createServer({
|
|
|
108
109
|
agentDir,
|
|
109
110
|
pluginRuntime,
|
|
110
111
|
containerName,
|
|
112
|
+
runtimeVersion,
|
|
111
113
|
tuiToken,
|
|
112
114
|
containerBroker,
|
|
113
115
|
logger = consoleLogger,
|
|
@@ -167,6 +169,7 @@ export function createServer({
|
|
|
167
169
|
...(channelRouter ? { channelRouter } : {}),
|
|
168
170
|
...(pluginsWiring ? { plugins: pluginsWiring } : {}),
|
|
169
171
|
...(containerName !== undefined ? { containerName } : {}),
|
|
172
|
+
...(runtimeVersion !== undefined ? { runtimeVersion } : {}),
|
|
170
173
|
})
|
|
171
174
|
const session = 'session' in result ? result.session : result
|
|
172
175
|
const dispose = 'session' in result && result.dispose ? result.dispose : async () => {}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: typeclaw-memory
|
|
3
|
-
description: Use this skill whenever the user asks what you remember, what you forgot, what you dreamed, why a fact is or isn't in your memory, when memory consolidation happens, or whenever you are about to read or write `MEMORY.md`, anything under `memory/`, or `memory/skills/`. Triggers include "what do you remember", "do you remember X", "forget that", "what did you dream", "when do you dream next", "why did you forget X", "edit MEMORY.md", "add to memory", "your daily streams", "memory-logger", "dreaming", "muscle memory", or any mention of `memory.idleMs` / `memory.dreaming.schedule` in `typeclaw.json`. Read it before you touch any memory file — `MEMORY.md` and `memory/yyyy-MM-dd.
|
|
3
|
+
description: Use this skill whenever the user asks what you remember, what you forgot, what you dreamed, why a fact is or isn't in your memory, when memory consolidation happens, or whenever you are about to read or write `MEMORY.md`, anything under `memory/`, or `memory/skills/`. Triggers include "what do you remember", "do you remember X", "forget that", "what did you dream", "when do you dream next", "why did you forget X", "edit MEMORY.md", "add to memory", "your daily streams", "memory-logger", "dreaming", "muscle memory", or any mention of `memory.idleMs` / `memory.dreaming.schedule` in `typeclaw.json`. Read it before you touch any memory file — `MEMORY.md` and `memory/yyyy-MM-dd.jsonl` are runtime-owned, hand-edits are easy to do wrong, and the user almost always means something more specific than "edit memory" when they say it.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# typeclaw-memory
|
|
7
7
|
|
|
8
|
-
You have a two-stage memory system, owned by the bundled `memory` plugin (auto-loaded on every TypeClaw agent — there is no `plugins[]` entry to add and no opt-out). Daily observations flow into `memory/yyyy-MM-dd.
|
|
8
|
+
You have a two-stage memory system, owned by the bundled `memory` plugin (auto-loaded on every TypeClaw agent — there is no `plugins[]` entry to add and no opt-out). Daily observations flow into `memory/yyyy-MM-dd.jsonl` while you are awake; offline reflection consolidates them into `MEMORY.md` and may distill repeated procedures into muscle-memory skills under `memory/skills/`. Both stages are run by subagents the runtime spawns on its own — not tools you call directly.
|
|
9
9
|
|
|
10
10
|
This skill exists so you can answer the user's questions about your own memory honestly and so you do not corrupt it by hand-editing.
|
|
11
11
|
|
|
@@ -18,7 +18,7 @@ After every prompt completes, the runtime fires the `session.idle` hook. The mem
|
|
|
18
18
|
The memory-logger reads:
|
|
19
19
|
|
|
20
20
|
1. `MEMORY.md` (long-term memory)
|
|
21
|
-
2. The current `memory/yyyy-MM-dd.
|
|
21
|
+
2. The current `memory/yyyy-MM-dd.jsonl` daily stream
|
|
22
22
|
3. The transcript of the parent session past a watermark (the `entry=` value of the last fragment or watermark marker for that session)
|
|
23
23
|
|
|
24
24
|
It writes zero or more **fragments** to today's stream, plus a watermark marker so the next run knows where to resume. It writes nothing else, and it cannot run shell commands or edit existing content (its only tools are `read` and a custom `append`-only file tool — append never truncates, and a leading `\n` is auto-inserted if the existing file did not end in one).
|
|
@@ -43,9 +43,9 @@ The dreaming subagent runs on cron, configured under `memory.dreaming.schedule`
|
|
|
43
43
|
When dreaming fires, it reads:
|
|
44
44
|
|
|
45
45
|
1. `MEMORY.md`
|
|
46
|
-
2. The **undreamed
|
|
46
|
+
2. The **undreamed fragments** of every `memory/yyyy-MM-dd.jsonl` (the runtime tells it which fragment ids are new — fragments whose ids are already in `memory/.dreaming-state.json#dreamedThrough[date].dreamedIds` have been consolidated and must NOT be re-cited)
|
|
47
47
|
|
|
48
|
-
It rewrites `MEMORY.md` with the merged result, advances the per-day
|
|
48
|
+
It rewrites `MEMORY.md` with the merged result, advances the per-day dreamed-id set in `memory/.dreaming-state.json`, optionally writes muscle-memory skills under `memory/skills/<name>/SKILL.md`, **compacts the touched daily streams** (drops superseded watermarks per source and fragments that are in `dreamedIds` but not cited from `MEMORY.md`), then commits the snapshot with a message shaped like `dream: <summary> <emoji>` — e.g. `dream: 3 fragments + new skill 'pr-review' 🔮`. The summary is derived from the staged diff (line additions in daily streams, newly-added skills, etc.), and the emoji is a random pick from a small thematic pool. After the commit, the runtime sets the `skip-worktree` index flag on the tracked memory artifacts so the user's `git status` and `git diff` stay clean. The flag is cleared and re-applied around every commit.
|
|
49
49
|
|
|
50
50
|
The dreaming subagent has only three tools: `read`, `write`, `ls`. No `bash`. No `edit`. It cannot run shell commands.
|
|
51
51
|
|
|
@@ -58,17 +58,17 @@ The dreaming subagent has only three tools: `read`, `write`, `ls`. No `bash`. No
|
|
|
58
58
|
<conclusion paragraph in dreaming's own words>
|
|
59
59
|
|
|
60
60
|
fragments:
|
|
61
|
-
- memory/yyyy-MM-dd
|
|
62
|
-
- memory/yyyy-MM-dd
|
|
61
|
+
- memory/yyyy-MM-dd#<fragment-id>
|
|
62
|
+
- memory/yyyy-MM-dd#<fragment-id>
|
|
63
63
|
|
|
64
64
|
## <topic>
|
|
65
65
|
<conclusion paragraph>
|
|
66
66
|
|
|
67
67
|
fragments:
|
|
68
|
-
- memory/yyyy-MM-dd
|
|
68
|
+
- memory/yyyy-MM-dd#<fragment-id>
|
|
69
69
|
```
|
|
70
70
|
|
|
71
|
-
The first line is always `# Memory`. Topics are level-2 headings. Every topic cites the source fragments by `memory/yyyy-MM-dd
|
|
71
|
+
The first line is always `# Memory`. Topics are level-2 headings. Every topic cites the source fragments by `memory/yyyy-MM-dd#<uuidv7>` (the full id from the fragment event's `id` field) so any claim is traceable back to the daily stream entry that justified it. Citations are id-based, not line-based, so daily streams can be compacted between dreaming runs without invalidating prior references.
|
|
72
72
|
|
|
73
73
|
If the undreamed tails contain only watermarks, or every new fragment is already represented in `MEMORY.md`, dreaming **does nothing** and exits without writing. The watermark advances either way. "No-op dreaming" is a normal outcome, not a failure.
|
|
74
74
|
|
|
@@ -77,7 +77,7 @@ If the undreamed tails contain only watermarks, or every new fragment is already
|
|
|
77
77
|
Core's `createResourceLoader` appends a `# Memory` section as the LAST block of your system prompt (after `gitNudge`) by calling `loadMemory`. It is pinned to the cache-suffix end so growth in the daily stream invalidates only the memory section itself, not the skills/tools/history above. The section contains:
|
|
78
78
|
|
|
79
79
|
- `MEMORY.md` (truncated to 12 KB; if larger, the rest is dropped with a `[truncated]` marker)
|
|
80
|
-
- The **undreamed tails** of each `memory/yyyy-MM-dd.
|
|
80
|
+
- The **undreamed tails** of each `memory/yyyy-MM-dd.jsonl`, with bare watermark lines stripped (they are bookkeeping for the memory-logger, no signal for you)
|
|
81
81
|
|
|
82
82
|
Already-consolidated content is not injected twice — once a day's stream is fully dreamed, the loader drops it from the prompt entirely.
|
|
83
83
|
|
|
@@ -86,9 +86,9 @@ If `MEMORY.md` is missing, the section shows `[MISSING] Expected at: <path>`. If
|
|
|
86
86
|
## What you must not do
|
|
87
87
|
|
|
88
88
|
- **Do not edit `MEMORY.md` directly.** It is dreaming-owned. The default system prompt says this verbatim. If you write to `MEMORY.md` from a normal session, your edit will survive only until the next dreaming run, which rewrites the file from scratch using the consolidation logic above. The user's intent is almost never "diff-edit `MEMORY.md`" — see "When the user asks ..." below for the right routings.
|
|
89
|
-
- **Do not write to `memory/yyyy-MM-dd.
|
|
89
|
+
- **Do not write to `memory/yyyy-MM-dd.jsonl`.** Daily streams are memory-logger's territory. The runtime reads watermarks out of these files; a hand-edit in the wrong place silently corrupts the cursor. (`memory/` is gitignored at the agent level but force-committed by the dreaming snapshot — your hand-edit there will not look untracked, but it will still be a bug.)
|
|
90
90
|
- **Do not write to `memory/skills/<name>/SKILL.md`.** That is the _muscle memory_ layer, owned exclusively by the dreaming subagent. The `typeclaw-skills` skill says the same thing from the skills-system angle; this skill says it from the memory angle. If you want a hand-authored skill, put it in `.agents/skills/` instead.
|
|
91
|
-
- **Do not write to `memory/.dreaming-state.json`.** It is internal bookkeeping (per-day
|
|
91
|
+
- **Do not write to `memory/.dreaming-state.json`.** It is internal bookkeeping (per-day dreamed-id sets). On malformed input the plugin fails open with empty state, so a wrong edit causes one redundant re-consolidation, but it is still a sign you misunderstood the contract.
|
|
92
92
|
- **Do not promise the user that an `idleMs` or `dreaming.schedule` change took effect just because you edited `typeclaw.json`.** Both fields are **restart-required** — the plugin reads them once at boot, and `reload` does not re-run plugin factories. Tell the user to run `typeclaw restart` (host stage).
|
|
93
93
|
- **Do not invent fragments.** If you find yourself wanting to "seed" a memory by hand, that is a symptom of the previous rules — surface the fact in your reply (so the memory-logger captures it) instead of writing to memory yourself.
|
|
94
94
|
- **Do not echo `[truncated]` or `[MISSING]` markers back at the user as if they were part of remembered content.** They are runtime annotations.
|
|
@@ -96,13 +96,13 @@ If `MEMORY.md` is missing, the section shows `[MISSING] Expected at: <path>`. If
|
|
|
96
96
|
## When the user asks "what do you remember?"
|
|
97
97
|
|
|
98
98
|
1. Read `MEMORY.md`. Summarize at the topic level — do not dump the whole file unless asked. Cite specific topics by their level-2 headings.
|
|
99
|
-
2. If relevant to the current task, also read the undreamed-tail of recent `memory/yyyy-MM-dd.
|
|
99
|
+
2. If relevant to the current task, also read the undreamed-tail of recent `memory/yyyy-MM-dd.jsonl` files for fresh observations not yet consolidated. (Note: these are already in your prompt under `# Memory`, so usually you can just refer to them rather than re-reading.)
|
|
100
100
|
3. If `MEMORY.md` is `[MISSING]` or `[EMPTY]`, say so plainly. The first dreaming run creates the file; if dreaming has never fired (e.g. no `memory.dreaming.schedule` configured, or fewer than ~24 hours since hatching), there is genuinely nothing yet.
|
|
101
101
|
|
|
102
102
|
## When the user asks "do you remember X?"
|
|
103
103
|
|
|
104
104
|
1. Search `MEMORY.md` and recent daily streams for a fragment matching X.
|
|
105
|
-
2. If you find one: say what you found and cite the source (the topic heading from `MEMORY.md`, or the
|
|
105
|
+
2. If you find one: say what you found and cite the source (the topic heading from `MEMORY.md`, or the `memory/yyyy-MM-dd#<id>` citation from the daily stream).
|
|
106
106
|
3. If you do not find one: say so plainly. **Do not invent a memory** to be helpful. The honest answer is "no, that is not in my memory" — the user can then decide whether to repeat the context now (which the memory-logger will pick up) or skip it.
|
|
107
107
|
|
|
108
108
|
## When the user asks "forget X" / "remove X from your memory"
|
|
@@ -125,7 +125,7 @@ Stay concrete. Use this map:
|
|
|
125
125
|
| File / dir | What it is | Who writes it | Tracked in git |
|
|
126
126
|
| ------------------------------- | ----------------------------------------------------------------------------- | -------------------------------------------------------------- | ------------------------------------------------------------ |
|
|
127
127
|
| `MEMORY.md` | Long-term memory, consolidated topics with fragment citations. | Dreaming subagent (rewrites in full on each run). | Yes (force-committed under `dream:` commits, skip-worktree). |
|
|
128
|
-
| `memory/yyyy-MM-dd.
|
|
128
|
+
| `memory/yyyy-MM-dd.jsonl` | Daily fragment streams. Append-only during the day. | Memory-logger subagent (one fragment ≈ one prompt completion). | Gitignored, but force-committed in the dreaming snapshot. |
|
|
129
129
|
| `memory/skills/<name>/SKILL.md` | Muscle-memory skills distilled from recurring procedures. | Dreaming subagent only. | Gitignored, force-committed in the dreaming snapshot. |
|
|
130
130
|
| `memory/.dreaming-state.json` | Per-day watermarks (line counts already consolidated). Plain JSON, fail-open. | Dreaming subagent. | Gitignored, force-committed in the dreaming snapshot. |
|
|
131
131
|
|