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 +65 -0
- package/.MOP/STATE.json +25 -0
- package/.MOP/scripts/mop-autosycn.mjs +122 -3
- package/.MOP/scripts/mop-core.mjs +230 -2
- package/.agents/AGENTS.md +7 -0
- package/AGENTS.md +9 -0
- package/CLAUDE.md +9 -0
- package/GEMINI.md +7 -0
- package/README.bm.md +1 -1
- package/README.md +1 -1
- package/package.json +1 -1
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
|
-
|
|
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.
|
|
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.
|
|
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
|