burhan-mop 0.1.5 → 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/.MOP/PROTOCOL.md CHANGED
@@ -60,6 +60,40 @@ Gate order:
60
60
  3. `AGENT_GATE`
61
61
  4. `ACTION`
62
62
 
63
+ ## Answer Contract
64
+
65
+ For every authenticated user-facing answer, the assistant must show the active
66
+ named agent before the content. This applies to short answers, explanations,
67
+ status updates, plans, code-change summaries, and final responses.
68
+
69
+ Required first line format:
70
+
71
+ ```text
72
+ agent: <agent-name> (<agent-role>) to <user>
73
+ ```
74
+
75
+ Rules:
76
+
77
+ - Do not answer authenticated work without an active named agent.
78
+ - If no active agent exists, ask the user to name the required agent and stop.
79
+ - After `agent route`, use `answerContract.firstLine` from the JSON response.
80
+ - Party Mode keeps the full party dialogue format, but the final handoff to the
81
+ user must still show the speaking agent.
82
+ - Never hide the selected agent in prose. The first visible line must identify
83
+ the agent.
84
+
85
+ Before answering, restore monthly memory:
86
+
87
+ ```bash
88
+ node .MOP/scripts/mop-core.mjs memory brief --actor <codename>
89
+ ```
90
+
91
+ After meaningful work or a useful answer, save memory:
92
+
93
+ ```bash
94
+ node .MOP/scripts/mop-core.mjs memory add --actor <codename> --kind conversation --summary "<one-line outcome>"
95
+ ```
96
+
63
97
  ## Agent Router
64
98
 
65
99
  After authentication and before the Agent Gate, route the user's task to one
@@ -88,6 +122,9 @@ Routing rules:
88
122
  genuinely requires several areas of expertise.
89
123
  - If the route reports `partyMode.active: true`, run Party Mode before the final
90
124
  answer. Name any missing participant agents first.
125
+ - The route JSON includes an `answerContract`. The assistant must restore
126
+ monthly memory, start the visible answer with `answerContract.firstLine`, and
127
+ save a one-line memory after meaningful work.
91
128
 
92
129
  Example:
93
130
 
@@ -282,6 +319,34 @@ The skill must call `mop-workflow.mjs help` and answer with the next action,
282
319
  lead agent, party agents, next artifact, and whether readiness/adversarial gates
283
320
  apply.
284
321
 
322
+ ## Monthly Memory
323
+
324
+ MOP keeps durable memory in two layers:
325
+
326
+ - `.MOP/STATE.json` ledger for compact structured state.
327
+ - `.MOP/memory/YYYY-MM.jsonl` for append-only monthly conversation memory.
328
+
329
+ The monthly memory file exists so a fresh chat can restore prior context without
330
+ the user explaining the whole project again. Each useful session must:
331
+
332
+ 1. Run `memory brief` before answering after authentication.
333
+ 2. Save one-line outcomes with `memory add`.
334
+ 3. Use `.MOP/memory/SESSION_BRIEF.md` as the short human-readable handoff.
335
+
336
+ Commands:
337
+
338
+ ```bash
339
+ node .MOP/scripts/mop-core.mjs memory brief --actor <codename>
340
+ node .MOP/scripts/mop-core.mjs memory restore --actor <codename>
341
+ node .MOP/scripts/mop-core.mjs memory add --actor <codename> --kind conversation --summary "<what happened>"
342
+ ```
343
+
344
+ Autosycn memory also writes to the monthly memory file:
345
+
346
+ ```bash
347
+ node .MOP/scripts/mop-autosycn.mjs memory --actor <codename> --summary "<what changed>"
348
+ ```
349
+
285
350
  ## Installer
286
351
 
287
352
  The package installer command is:
package/.MOP/STATE.json CHANGED
@@ -13,6 +13,31 @@
13
13
  "defaultTitle": "Core Agent",
14
14
  "gateOrder": "AUTH_GATE_THEN_AGENT_ROUTER_THEN_AGENT_GATE_THEN_ACTION"
15
15
  },
16
+ "answerPolicy": {
17
+ "requireVisibleAgent": true,
18
+ "visibleAgentFormat": "agent: <agent-name> (<agent-role>) to <user>",
19
+ "requireMemoryRestore": true,
20
+ "requireMemorySave": true,
21
+ "rules": [
22
+ "Every authenticated user-facing answer must start with the active named agent line.",
23
+ "If no active named agent exists, ask the user to name the required agent before answering the task.",
24
+ "Party Mode keeps its dialogue format, but the final handoff to the user must still show the speaking agent."
25
+ ]
26
+ },
27
+ "memoryPolicy": {
28
+ "enabled": true,
29
+ "directory": ".MOP/memory",
30
+ "sessionBrief": ".MOP/memory/SESSION_BRIEF.md",
31
+ "monthlyPattern": "YYYY-MM.jsonl",
32
+ "recentLimit": 20,
33
+ "restoreBeforeAnswer": true,
34
+ "saveAfterMeaningfulWork": true,
35
+ "rules": [
36
+ "Save one-line memories to a monthly JSONL file so new chats can restore context.",
37
+ "Generate SESSION_BRIEF.md from recent monthly memory entries.",
38
+ "Read memory brief before answering any authenticated task."
39
+ ]
40
+ },
16
41
  "agentRouter": {
17
42
  "enabled": true,
18
43
  "primaryAgentLimit": 1,
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { existsSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
2
+ import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, writeFileSync } from 'node:fs';
3
3
  import { dirname, join, resolve } from 'node:path';
4
4
  import { fileURLToPath } from 'node:url';
5
5
  import { spawnSync } from 'node:child_process';
@@ -109,6 +109,123 @@ function agentLedgerFields(agent) {
109
109
  } : {};
110
110
  }
111
111
 
112
+ function memoryPolicy(state) {
113
+ return state.memoryPolicy || {
114
+ enabled: true,
115
+ directory: '.MOP/memory',
116
+ sessionBrief: '.MOP/memory/SESSION_BRIEF.md',
117
+ monthlyPattern: 'YYYY-MM.jsonl',
118
+ recentLimit: 20
119
+ };
120
+ }
121
+
122
+ function answerPolicy(state) {
123
+ return state.answerPolicy || {
124
+ requireVisibleAgent: true,
125
+ visibleAgentFormat: 'agent: <agent-name> (<agent-role>) to <user>'
126
+ };
127
+ }
128
+
129
+ function monthKey(date = new Date()) {
130
+ return date.toISOString().slice(0, 7);
131
+ }
132
+
133
+ function memoryDirFor(state) {
134
+ return join(rootDir, memoryPolicy(state).directory || '.MOP/memory');
135
+ }
136
+
137
+ function monthlyMemoryPath(state, month = monthKey()) {
138
+ const policy = memoryPolicy(state);
139
+ const filename = (policy.monthlyPattern || 'YYYY-MM.jsonl').replace('YYYY-MM', month);
140
+ return join(memoryDirFor(state), filename);
141
+ }
142
+
143
+ function sessionBriefPath(state) {
144
+ return join(rootDir, memoryPolicy(state).sessionBrief || '.MOP/memory/SESSION_BRIEF.md');
145
+ }
146
+
147
+ function readJsonl(path) {
148
+ if (!existsSync(path)) return [];
149
+ return readFileSync(path, 'utf8')
150
+ .split(/\r?\n/)
151
+ .filter(Boolean)
152
+ .map((line) => {
153
+ try {
154
+ return JSON.parse(line);
155
+ } catch {
156
+ return null;
157
+ }
158
+ })
159
+ .filter(Boolean);
160
+ }
161
+
162
+ function memoryMonths(state) {
163
+ const dir = memoryDirFor(state);
164
+ if (!existsSync(dir)) return [];
165
+ return readdirSync(dir)
166
+ .filter((name) => /^\d{4}-\d{2}\.jsonl$/.test(name))
167
+ .map((name) => name.replace(/\.jsonl$/, ''))
168
+ .sort();
169
+ }
170
+
171
+ function latestMemoryEntries(state, limit = memoryPolicy(state).recentLimit || 20) {
172
+ const months = memoryMonths(state);
173
+ const selected = months.length ? months.slice(-3) : [monthKey()];
174
+ return selected
175
+ .flatMap((month) => readJsonl(monthlyMemoryPath(state, month)))
176
+ .sort((a, b) => String(a.at || '').localeCompare(String(b.at || '')))
177
+ .slice(-limit);
178
+ }
179
+
180
+ function answerLineFor(state, actor, agent = activeAgentFor(state, actor)) {
181
+ if (!agent) return 'agent: <name> (<role>) to <user>';
182
+ return (answerPolicy(state).visibleAgentFormat || 'agent: <agent-name> (<agent-role>) to <user>')
183
+ .replace('<agent-name>', agent.name)
184
+ .replace('<agent-role>', agent.role)
185
+ .replace('<agent-title>', agent.title || agent.role)
186
+ .replace('<user>', actor || 'user');
187
+ }
188
+
189
+ function appendMonthlyMemory(state, actor, kind, summary, agent = activeAgentFor(state, actor)) {
190
+ if (memoryPolicy(state).enabled === false) return;
191
+ const entry = { at: now(), actor, ...agentLedgerFields(agent), kind, summary };
192
+ const monthlyPath = monthlyMemoryPath(state);
193
+ mkdirSync(dirname(monthlyPath), { recursive: true });
194
+ writeFileSync(monthlyPath, `${JSON.stringify(entry)}\n`, { encoding: 'utf8', flag: 'a' });
195
+ writeSessionBrief(state, actor);
196
+ }
197
+
198
+ function writeSessionBrief(state, actor) {
199
+ const path = sessionBriefPath(state);
200
+ const agent = activeAgentFor(state, actor);
201
+ const entries = latestMemoryEntries(state, memoryPolicy(state).recentLimit || 20);
202
+ const lines = [
203
+ '# MOP Session Brief',
204
+ '',
205
+ `Updated: ${now()}`,
206
+ `Actor: ${actor || state.activeMember || 'unknown'}`,
207
+ `Active agent: ${agent ? `${agent.name} (${agent.role})` : 'none'}`,
208
+ `Current month: ${monthKey()}`,
209
+ '',
210
+ '## Required Session Flow',
211
+ '',
212
+ '1. Read `.MOP/STATE.json` and follow `.MOP/PROTOCOL.md`.',
213
+ '2. Restore memory with `node .MOP/scripts/mop-core.mjs memory brief --actor <codename>`.',
214
+ '3. Run `agent route` for the user task before answering.',
215
+ `4. Start every authenticated answer with: \`${answerLineFor(state, actor, agent)}\``,
216
+ '5. Save a one-line memory after meaningful work.',
217
+ '',
218
+ '## Recent Memory',
219
+ '',
220
+ ...entries.map((entry) => {
221
+ const who = entry.agent ? `${entry.agent} (${entry.agentRole || 'agent'})` : entry.actor;
222
+ return `- ${entry.at} - ${who}: ${entry.summary}`;
223
+ })
224
+ ];
225
+ mkdirSync(dirname(path), { recursive: true });
226
+ writeFileSync(path, `${lines.join('\n')}\n`, 'utf8');
227
+ }
228
+
112
229
  function appendLedger(state, actor, kind, summary, agent = activeAgentFor(state, actor)) {
113
230
  state.ledger ||= [];
114
231
  state.ledger.push({ at: now(), actor, ...agentLedgerFields(agent), kind, summary });
@@ -392,10 +509,11 @@ function preflight(args) {
392
509
  }, null, 2));
393
510
  }
394
511
 
395
- function saveMemory(actor, summary) {
512
+ function saveMemory(actor, summary, kind = 'conversation') {
396
513
  const state = readState();
397
514
  const agent = requireActiveAgent(state, actor);
398
515
  appendLedger(state, actor, 'memory', summary, agent);
516
+ appendMonthlyMemory(state, actor, kind, summary, agent);
399
517
  writeState(state);
400
518
  return state;
401
519
  }
@@ -588,7 +706,8 @@ function main() {
588
706
  if (command === 'memory') {
589
707
  const actor = requireArg(args, 'actor');
590
708
  const summary = String(args.summary || args.reason || 'MOP conversation');
591
- saveMemory(actor, summary);
709
+ const kind = String(args.kind || 'conversation');
710
+ saveMemory(actor, summary, kind);
592
711
  console.log(`Memory saved for ${actor}.`);
593
712
  return;
594
713
  }
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
2
+ import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, writeFileSync } from 'node:fs';
3
3
  import { dirname, join, resolve } from 'node:path';
4
4
  import { fileURLToPath } from 'node:url';
5
5
  import { randomBytes, scryptSync, timingSafeEqual } from 'node:crypto';
@@ -87,6 +87,103 @@ function agentLedgerFields(agent) {
87
87
  } : {};
88
88
  }
89
89
 
90
+ function memoryPolicy(state) {
91
+ return state.memoryPolicy || {
92
+ enabled: true,
93
+ directory: '.MOP/memory',
94
+ sessionBrief: '.MOP/memory/SESSION_BRIEF.md',
95
+ monthlyPattern: 'YYYY-MM.jsonl',
96
+ recentLimit: 20
97
+ };
98
+ }
99
+
100
+ function answerPolicy(state) {
101
+ return state.answerPolicy || {
102
+ requireVisibleAgent: true,
103
+ visibleAgentFormat: 'agent: <agent-name> (<agent-role>) to <user>',
104
+ requireMemoryRestore: true,
105
+ requireMemorySave: true
106
+ };
107
+ }
108
+
109
+ function monthKey(date = new Date()) {
110
+ return date.toISOString().slice(0, 7);
111
+ }
112
+
113
+ function relativeFromRoot(path) {
114
+ return path.replace(rootDir, '').replace(/^[\\/]/, '').replaceAll('\\', '/');
115
+ }
116
+
117
+ function memoryDirFor(state) {
118
+ return join(rootDir, memoryPolicy(state).directory || '.MOP/memory');
119
+ }
120
+
121
+ function monthlyMemoryPath(state, month = monthKey()) {
122
+ const policy = memoryPolicy(state);
123
+ const filename = (policy.monthlyPattern || 'YYYY-MM.jsonl').replace('YYYY-MM', month);
124
+ return join(memoryDirFor(state), filename);
125
+ }
126
+
127
+ function sessionBriefPath(state) {
128
+ return join(rootDir, memoryPolicy(state).sessionBrief || '.MOP/memory/SESSION_BRIEF.md');
129
+ }
130
+
131
+ function readJsonl(path) {
132
+ if (!existsSync(path)) return [];
133
+ return readFileSync(path, 'utf8')
134
+ .split(/\r?\n/)
135
+ .filter(Boolean)
136
+ .map((line) => {
137
+ try {
138
+ return JSON.parse(line);
139
+ } catch {
140
+ return null;
141
+ }
142
+ })
143
+ .filter(Boolean);
144
+ }
145
+
146
+ function memoryMonths(state) {
147
+ const dir = memoryDirFor(state);
148
+ if (!existsSync(dir)) return [];
149
+ return readdirSync(dir)
150
+ .filter((name) => /^\d{4}-\d{2}\.jsonl$/.test(name))
151
+ .map((name) => name.replace(/\.jsonl$/, ''))
152
+ .sort();
153
+ }
154
+
155
+ function latestMemoryEntries(state, limit = memoryPolicy(state).recentLimit || 20) {
156
+ const months = memoryMonths(state);
157
+ const selected = months.length ? months.slice(-3) : [monthKey()];
158
+ return selected
159
+ .flatMap((month) => readJsonl(monthlyMemoryPath(state, month)))
160
+ .sort((a, b) => String(a.at || '').localeCompare(String(b.at || '')))
161
+ .slice(-limit);
162
+ }
163
+
164
+ function answerContractFor(state, actor, agent = activeAgentFor(state, actor)) {
165
+ const policy = answerPolicy(state);
166
+ const format = policy.visibleAgentFormat || 'agent: <agent-name> (<agent-role>) to <user>';
167
+ const firstLine = agent
168
+ ? format
169
+ .replace('<agent-name>', agent.name)
170
+ .replace('<agent-role>', agent.role)
171
+ .replace('<agent-title>', agent.title || agent.role)
172
+ .replace('<user>', actor || 'user')
173
+ : '';
174
+ return {
175
+ required: policy.requireVisibleAgent !== false,
176
+ firstLine,
177
+ beforeAnswer: `node .MOP/scripts/mop-core.mjs memory brief --actor ${actor || '<codename>'}`,
178
+ afterAnswer: `node .MOP/scripts/mop-core.mjs memory add --actor ${actor || '<codename>'} --kind conversation --summary "<one-line outcome>"`,
179
+ rules: [
180
+ 'Do not answer authenticated work without an active named agent.',
181
+ 'Every user-facing answer must show the active agent line first.',
182
+ 'Restore monthly memory before answering and save a one-line memory after meaningful work.'
183
+ ]
184
+ };
185
+ }
186
+
90
187
  function requireActiveAgent(state, actor, role = 'core', title = 'Core Agent') {
91
188
  const agent = activeAgentFor(state, actor);
92
189
  if (agent) return agent;
@@ -102,6 +199,48 @@ function appendLedger(state, actor, kind, summary, agent = activeAgentFor(state,
102
199
  state.ledger.push({ at: now(), actor, ...agentLedgerFields(agent), kind, summary });
103
200
  }
104
201
 
202
+ function appendMonthlyMemory(state, actor, kind, summary, agent = activeAgentFor(state, actor)) {
203
+ if (memoryPolicy(state).enabled === false) return null;
204
+ const entry = { at: now(), actor, ...agentLedgerFields(agent), kind, summary };
205
+ const monthlyPath = monthlyMemoryPath(state);
206
+ mkdirSync(dirname(monthlyPath), { recursive: true });
207
+ writeFileSync(monthlyPath, `${JSON.stringify(entry)}\n`, { encoding: 'utf8', flag: 'a' });
208
+ writeSessionBrief(state, actor);
209
+ return { entry, monthlyPath };
210
+ }
211
+
212
+ function writeSessionBrief(state, actor) {
213
+ const path = sessionBriefPath(state);
214
+ const agent = activeAgentFor(state, actor);
215
+ const entries = latestMemoryEntries(state, memoryPolicy(state).recentLimit || 20);
216
+ const contract = answerContractFor(state, actor, agent);
217
+ const lines = [
218
+ '# MOP Session Brief',
219
+ '',
220
+ `Updated: ${now()}`,
221
+ `Actor: ${actor || state.activeMember || 'unknown'}`,
222
+ `Active agent: ${agent ? `${agent.name} (${agent.role})` : 'none'}`,
223
+ `Current month: ${monthKey()}`,
224
+ '',
225
+ '## Required Session Flow',
226
+ '',
227
+ '1. Read `.MOP/STATE.json` and follow `.MOP/PROTOCOL.md`.',
228
+ '2. Authenticate if required.',
229
+ '3. Run `agent route` for the user task before answering.',
230
+ `4. Start every authenticated answer with: \`${contract.firstLine || 'agent: <name> (<role>) to <user>'}\``,
231
+ '5. Save a one-line memory after meaningful work.',
232
+ '',
233
+ '## Recent Memory',
234
+ '',
235
+ ...entries.map((entry) => {
236
+ const who = entry.agent ? `${entry.agent} (${entry.agentRole || 'agent'})` : entry.actor;
237
+ return `- ${entry.at} - ${who}: ${entry.summary}`;
238
+ })
239
+ ];
240
+ mkdirSync(dirname(path), { recursive: true });
241
+ writeFileSync(path, `${lines.join('\n')}\n`, 'utf8');
242
+ }
243
+
105
244
  const routeRules = [
106
245
  {
107
246
  role: 'memory',
@@ -435,6 +574,12 @@ function login(args) {
435
574
  console.log(`Active member: ${codename}`);
436
575
  if (!activeAgentFor(state, codename) && state.agentPolicy?.requiredAfterAuth !== false) {
437
576
  console.log(`Agent diperlukan. Jalankan: node .MOP/scripts/mop-core.mjs agent activate --actor ${codename} --role ${state.agentPolicy?.defaultRole || 'core'} --title "${state.agentPolicy?.defaultTitle || 'Core Agent'}" --name "<agent-name>"`);
577
+ } else {
578
+ console.log(JSON.stringify({
579
+ next: 'restore-memory-and-route-task',
580
+ memoryRestore: `node .MOP/scripts/mop-core.mjs memory brief --actor ${codename}`,
581
+ answerContract: answerContractFor(state, codename)
582
+ }, null, 2));
438
583
  }
439
584
  }
440
585
 
@@ -553,6 +698,11 @@ function agentRoute(args) {
553
698
  route,
554
699
  partyAgents,
555
700
  activeAgent: null,
701
+ answerContract: null,
702
+ monthlyMemory: {
703
+ restoreCommand: `node .MOP/scripts/mop-core.mjs memory brief --actor ${actor}`,
704
+ saveCommand: `node .MOP/scripts/mop-core.mjs memory add --actor ${actor} --kind conversation --summary "<one-line outcome>"`
705
+ },
556
706
  nextAction: null
557
707
  };
558
708
 
@@ -567,6 +717,7 @@ function agentRoute(args) {
567
717
  role: agent.role,
568
718
  title: agent.title
569
719
  };
720
+ response.answerContract = answerContractFor(state, actor, agent);
570
721
  if (route.partyMode.active && missingPartyAgents.length) {
571
722
  response.ok = false;
572
723
  response.nextAction = 'name-required-party-agents';
@@ -596,6 +747,73 @@ function agentList() {
596
747
  }, null, 2));
597
748
  }
598
749
 
750
+ function memoryAdd(args) {
751
+ const state = readState();
752
+ if (!state.initialized) throw new Error('MOP is not initialized.');
753
+ const actor = slug(requireArg(args, 'actor'));
754
+ if (!state.members?.[actor]) throw new Error('Unknown actor.');
755
+ const agent = requireActiveAgent(state, actor);
756
+ const summary = String(args.summary || args._?.join(' ') || '').trim();
757
+ const kind = String(args.kind || 'conversation');
758
+ if (!summary) throw new Error('Missing --summary');
759
+
760
+ appendLedger(state, actor, 'memory', summary, agent);
761
+ const saved = appendMonthlyMemory(state, actor, kind, summary, agent);
762
+ writeState(state);
763
+ console.log(JSON.stringify({
764
+ ok: true,
765
+ actor,
766
+ agent: agent.name,
767
+ kind,
768
+ summary,
769
+ monthlyMemory: saved ? relativeFromRoot(saved.monthlyPath) : 'disabled',
770
+ sessionBrief: relativeFromRoot(sessionBriefPath(state)),
771
+ answerContract: answerContractFor(state, actor, agent)
772
+ }, null, 2));
773
+ }
774
+
775
+ function memoryBrief(args) {
776
+ const state = readState();
777
+ if (!state.initialized) {
778
+ console.log('MOP belum di-setup. Jalankan /mop-setup.');
779
+ return;
780
+ }
781
+ const actor = slug(String(args.actor || state.activeMember || ''));
782
+ if (!actor) throw new Error('Missing --actor');
783
+ if (!state.members?.[actor]) throw new Error('Unknown actor.');
784
+ const agent = activeAgentFor(state, actor);
785
+ const month = String(args.month || monthKey());
786
+ const limit = Number(args.limit || memoryPolicy(state).recentLimit || 20);
787
+ const currentEntries = readJsonl(monthlyMemoryPath(state, month)).slice(-limit);
788
+ const recentEntries = latestMemoryEntries(state, limit);
789
+ if (agent) writeSessionBrief(state, actor);
790
+ console.log(JSON.stringify({
791
+ ok: true,
792
+ actor,
793
+ activeAgent: agent ? {
794
+ id: agent.id,
795
+ name: agent.name,
796
+ role: agent.role,
797
+ title: agent.title
798
+ } : null,
799
+ answerContract: answerContractFor(state, actor, agent),
800
+ memory: {
801
+ month,
802
+ monthPath: relativeFromRoot(monthlyMemoryPath(state, month)),
803
+ sessionBrief: relativeFromRoot(sessionBriefPath(state)),
804
+ currentMonthEntries: currentEntries,
805
+ recentEntries
806
+ },
807
+ next: agent
808
+ ? 'Use answerContract.firstLine before answering, then save memory after meaningful work.'
809
+ : `Agent diperlukan. Jalankan: node .MOP/scripts/mop-core.mjs agent activate --actor ${actor} --role ${state.agentPolicy?.defaultRole || 'core'} --title "${state.agentPolicy?.defaultTitle || 'Core Agent'}" --name "<agent-name>"`
810
+ }, null, 2));
811
+ }
812
+
813
+ function memoryRestore(args) {
814
+ return memoryBrief(args);
815
+ }
816
+
599
817
  function memberGitIdentity(args) {
600
818
  const state = readState();
601
819
  if (!state.initialized) throw new Error('MOP is not initialized.');
@@ -621,6 +839,8 @@ function validate() {
621
839
  if (!Array.isArray(state.agentCatalog)) errors.push('agentCatalog must be array');
622
840
  if (state.activeAgents && typeof state.activeAgents !== 'object') errors.push('activeAgents must be object');
623
841
  if (state.agentPolicy && typeof state.agentPolicy !== 'object') errors.push('agentPolicy must be object');
842
+ if (state.answerPolicy && typeof state.answerPolicy !== 'object') errors.push('answerPolicy must be object');
843
+ if (state.memoryPolicy && typeof state.memoryPolicy !== 'object') errors.push('memoryPolicy must be object');
624
844
  if (state.agentRouter && typeof state.agentRouter !== 'object') errors.push('agentRouter must be object');
625
845
  if (state.partyMode && typeof state.partyMode !== 'object') errors.push('partyMode must be object');
626
846
  if (state.projectRootPolicy && typeof state.projectRootPolicy !== 'object') errors.push('projectRootPolicy must be object');
@@ -703,6 +923,8 @@ function status() {
703
923
  return [codename, agent ? { name: agent.name, role: agent.role, id: agent.id } : { id: agentId, missing: true }];
704
924
  })),
705
925
  agentPolicy: state.agentPolicy || {},
926
+ answerPolicy: answerPolicy(state),
927
+ memoryPolicy: memoryPolicy(state),
706
928
  agentRouter: state.agentRouter || {},
707
929
  partyMode: state.partyMode || {},
708
930
  workflow: {
@@ -756,6 +978,9 @@ function main() {
756
978
  if (command === 'agent' && subcommand === 'require') return agentRequire(args);
757
979
  if (command === 'agent' && subcommand === 'route') return agentRoute(args);
758
980
  if (command === 'agent' && subcommand === 'list') return agentList();
981
+ if (command === 'memory' && subcommand === 'add') return memoryAdd(args);
982
+ if (command === 'memory' && subcommand === 'brief') return memoryBrief(args);
983
+ if (command === 'memory' && subcommand === 'restore') return memoryRestore(args);
759
984
 
760
985
  console.log(`Usage:
761
986
  node .MOP/scripts/mop-core.mjs status
@@ -768,7 +993,10 @@ function main() {
768
993
  node .MOP/scripts/mop-core.mjs agent current --actor CODE
769
994
  node .MOP/scripts/mop-core.mjs agent require --actor CODE [--role ROLE] [--title TITLE]
770
995
  node .MOP/scripts/mop-core.mjs agent route --actor CODE --task "task text"
771
- node .MOP/scripts/mop-core.mjs agent list`);
996
+ node .MOP/scripts/mop-core.mjs agent list
997
+ node .MOP/scripts/mop-core.mjs memory brief --actor CODE [--month YYYY-MM]
998
+ node .MOP/scripts/mop-core.mjs memory add --actor CODE --kind conversation --summary "what happened"
999
+ node .MOP/scripts/mop-core.mjs memory restore --actor CODE`);
772
1000
  }
773
1001
 
774
1002
  try {
package/.agents/AGENTS.md CHANGED
@@ -14,6 +14,13 @@ Before doing anything, read `.MOP/STATE.json` and follow
14
14
  - After login, run the Agent Router: every conversation and action must route
15
15
  to one primary named agent. Check with `mop-core.mjs agent route --actor
16
16
  <codename> --task "<user task>"`.
17
+ - Before answering, restore monthly memory with
18
+ `mop-core.mjs memory brief --actor <codename>`.
19
+ - Every authenticated user-facing answer must start with the routed
20
+ `answerContract.firstLine`, for example:
21
+ `agent: <agent-name> (<agent-role>) to <user>`.
22
+ - After meaningful work, save memory with
23
+ `mop-core.mjs memory add --actor <codename> --kind conversation --summary "<outcome>"`.
17
24
  - If the router says clarification is needed, ask the clarifying questions
18
25
  before implementation.
19
26
  - If the router activates Party Mode, show visible agent-to-agent dialogue using
package/AGENTS.md CHANGED
@@ -14,6 +14,13 @@ follow `.MOP/PROTOCOL.md`.
14
14
  It selects one primary role and may recommend any number of support roles
15
15
  when genuinely needed. If no named agent exists for a needed role, ask the
16
16
  user to name it and save it with `agent activate`.
17
+ - Before answering an authenticated task, restore monthly memory:
18
+ `mop-core.mjs memory brief --actor <codename>`.
19
+ - Every authenticated user-facing answer must start with the routed named agent
20
+ line from `answerContract.firstLine`, for example:
21
+ `agent: <agent-name> (<agent-role>) to <user>`.
22
+ - After meaningful work or a useful answer, save memory:
23
+ `mop-core.mjs memory add --actor <codename> --kind conversation --summary "<one-line outcome>"`.
17
24
 
18
25
  | Role | Name | Description |
19
26
  | :--- | :--- | :--- |
@@ -49,6 +56,8 @@ different agents.
49
56
  The active named agent must be recorded on every memory/ledger action. Do not
50
57
  activate irrelevant agents; route to one primary agent first, then add support
51
58
  agents through Party Mode only when useful.
59
+ If the assistant cannot show the active agent line, it must stop and repair the
60
+ agent route instead of answering invisibly.
52
61
 
53
62
  Default skill: `autosycn`. After meaningful changes, use
54
63
  `.MOP/scripts/mop-autosycn.mjs`. It must commit and merge with the
package/CLAUDE.md CHANGED
@@ -14,6 +14,13 @@ follow `.MOP/PROTOCOL.md`.
14
14
  It selects one primary role and may recommend any number of support roles
15
15
  when genuinely needed. If no named agent exists for a needed role, ask the
16
16
  user to name it and save it with `agent activate`.
17
+ - Before answering an authenticated task, restore monthly memory:
18
+ `mop-core.mjs memory brief --actor <codename>`.
19
+ - Every authenticated user-facing answer must start with the routed named agent
20
+ line from `answerContract.firstLine`, for example:
21
+ `agent: <agent-name> (<agent-role>) to <user>`.
22
+ - After meaningful work or a useful answer, save memory:
23
+ `mop-core.mjs memory add --actor <codename> --kind conversation --summary "<one-line outcome>"`.
17
24
  - If the router marks the task as ambiguous, the named primary agent must ask
18
25
  clarifying questions before implementation.
19
26
  - If the router returns `partyMode.active: true`, use Party Mode. Show
@@ -34,6 +41,8 @@ different agents.
34
41
  The active named agent must be recorded on every memory/ledger action. Do not
35
42
  activate irrelevant agents; route to one primary agent first, then add support
36
43
  agents through Party Mode only when useful.
44
+ If the assistant cannot show the active agent line, it must stop and repair the
45
+ agent route instead of answering invisibly.
37
46
 
38
47
  Default skill: `autosycn`. After meaningful changes, use
39
48
  `.MOP/scripts/mop-autosycn.mjs`. It must commit and merge with the
package/GEMINI.md CHANGED
@@ -9,6 +9,13 @@ rules are imported below.
9
9
 
10
10
  - First action still applies: read `.MOP/STATE.json` and follow
11
11
  `.MOP/PROTOCOL.md`.
12
+ - After authentication, run `mop-core.mjs memory brief --actor <codename>` and
13
+ `mop-core.mjs agent route --actor <codename> --task "<user task>"` before
14
+ answering.
15
+ - Every authenticated answer must start with `answerContract.firstLine`, for
16
+ example: `agent: <agent-name> (<agent-role>) to <user>`.
17
+ - Save a one-line memory after meaningful work with
18
+ `mop-core.mjs memory add --actor <codename> --kind conversation --summary "<outcome>"`.
12
19
  - Treat the current workspace root as the project root; do not create a nested
13
20
  project wrapper folder for app work unless the user explicitly asks for it.
14
21
  - Use `.gemini/settings.json` for context filenames and MCP server setup.
package/README.bm.md CHANGED
@@ -142,7 +142,7 @@ dan merge hanya bila workflow selamat.
142
142
  | --- | --- |
143
143
  | npm package | [`burhan-mop`](https://www.npmjs.com/package/burhan-mop) |
144
144
  | command latest | `npx burhan-mop install` |
145
- | GitHub release | [`v0.1.5`](https://github.com/BURHANDEV-ENTERPRISE/BURHAN-MOP/releases/tag/v0.1.5) |
145
+ | GitHub release | [`v0.1.6`](https://github.com/BURHANDEV-ENTERPRISE/BURHAN-MOP/releases/tag/v0.1.6) |
146
146
  | Node | `>=20` |
147
147
 
148
148
  ## Links
package/README.md CHANGED
@@ -141,7 +141,7 @@ and merges only when the workflow is safe.
141
141
  | --- | --- |
142
142
  | npm package | [`burhan-mop`](https://www.npmjs.com/package/burhan-mop) |
143
143
  | latest command | `npx burhan-mop install` |
144
- | GitHub release | [`v0.1.5`](https://github.com/BURHANDEV-ENTERPRISE/BURHAN-MOP/releases/tag/v0.1.5) |
144
+ | GitHub release | [`v0.1.6`](https://github.com/BURHANDEV-ENTERPRISE/BURHAN-MOP/releases/tag/v0.1.6) |
145
145
  | Node | `>=20` |
146
146
 
147
147
  ## Links
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "burhan-mop",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "MOP portable agent memory core installer and CLI.",
5
5
  "type": "module",
6
6
  "author": "BURHANDEV ENTERPRISE",