Skip to content

Commit 3beb9c4

Browse files
Add shared OMP roles and memory dream review (#136)
* add shared OMP roles and memory dream review * address PR #136 review: accurate stale-duplicate reason wording + truncation regression test --------- Co-authored-by: Kirill Korikov <11762090+yourconscience@users.noreply.github.com>
1 parent a5c9a11 commit 3beb9c4

7 files changed

Lines changed: 741 additions & 2 deletions

File tree

agents/researcher.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ tools: [Read, Glob, Grep, Bash, WebFetch, WebSearch, Write]
77
color: green
88
codex:
99
model: gpt-5.6-luna
10-
model_reasoning_effort: medium
10+
model_reasoning_effort: max
11+
omp:
12+
model: gpt-5.6-luna
13+
thinking-level: max
1114
---
1215

1316
You are a technical researcher. Your job is to investigate and report, not implement.

cmd/dotagents/agents.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ type agentRole struct {
3535
Instructions string `yaml:"-"`
3636
Source string `yaml:"-"`
3737
Codex codexRoleOptions `yaml:"codex"`
38+
OMP ompRoleOptions `yaml:"omp"`
3839
Droid droidRoleOptions `yaml:"droid"`
3940
Opencode opencodeRoleOptions `yaml:"opencode"`
4041
}
@@ -71,6 +72,10 @@ func (role *agentRole) UnmarshalYAML(value *yaml.Node) error {
7172
if err := node.Decode(&role.Codex); err != nil {
7273
return err
7374
}
75+
case "omp":
76+
if err := node.Decode(&role.OMP); err != nil {
77+
return err
78+
}
7479
case "droid":
7580
if err := node.Decode(&role.Droid); err != nil {
7681
return err
@@ -116,6 +121,11 @@ type codexRoleOptions struct {
116121
ModelReasoningEffort string `yaml:"model_reasoning_effort"`
117122
}
118123

124+
type ompRoleOptions struct {
125+
Model string `yaml:"model"`
126+
ThinkingLevel string `yaml:"thinking-level"`
127+
}
128+
119129
type droidRoleOptions struct {
120130
Model string `yaml:"model"`
121131
ReasoningEffort string `yaml:"reasoning_effort"`

cmd/dotagents/agents_test.go

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,53 @@ func TestRenderCodexAgentRoleEscapesControlCharacters(t *testing.T) {
6262
}
6363
}
6464

65+
func TestRenderOMPAgentRoleHonorsOverride(t *testing.T) {
66+
role := agentRole{
67+
Name: "researcher",
68+
Description: "Find reliable evidence",
69+
Model: "opus",
70+
Effort: "high",
71+
Instructions: "Compare the sources.",
72+
OMP: ompRoleOptions{
73+
Model: "gpt-5.6-luna",
74+
ThinkingLevel: "xhigh",
75+
},
76+
}
77+
78+
got := renderOMPAgentRole(role)
79+
for _, want := range []string{
80+
`name: "researcher"`,
81+
"model:\n",
82+
`- "gpt-5.6-luna"`,
83+
`thinking-level: "xhigh"`,
84+
} {
85+
if !strings.Contains(got, want) {
86+
t.Fatalf("rendered OMP role missing %q:\n%s", want, got)
87+
}
88+
}
89+
if strings.Contains(got, `- "opus"`) || strings.Contains(got, "effort:") {
90+
t.Fatalf("OMP role leaked Claude defaults into override:\n%s", got)
91+
}
92+
}
93+
94+
func TestRenderOMPAgentRoleFallsBackToCanonicalModel(t *testing.T) {
95+
role := agentRole{
96+
Name: "reviewer",
97+
Description: "Reviews code",
98+
Model: "opus",
99+
Effort: "high",
100+
Instructions: "Review carefully.",
101+
}
102+
103+
got := renderOMPAgentRole(role)
104+
if !strings.Contains(got, "model:\n") || !strings.Contains(got, `- "opus"`) {
105+
t.Fatalf("OMP role did not fall back to canonical model:\n%s", got)
106+
}
107+
if strings.Contains(got, "thinking-level:") {
108+
t.Fatalf("OMP role invented a thinking-level without an override:\n%s", got)
109+
}
110+
}
111+
65112
func TestRenderDroidAgentRoleMapsModelAndTools(t *testing.T) {
66113
role := agentRole{
67114
Name: "builder",
@@ -173,6 +220,9 @@ model: sonnet
173220
effort: high
174221
tools: [Read, Grep]
175222
color: purple
223+
omp:
224+
model: gpt-5.6-luna
225+
thinking-level: xhigh
176226
---
177227
178228
Review the change.
@@ -207,10 +257,58 @@ Implement the change.
207257
if len(role.Tools) != 2 || role.Tools[0] != "Read" || role.Tools[1] != "Grep" {
208258
t.Fatalf("unexpected tools: %#v", role.Tools)
209259
}
260+
if role.OMP.Model != "gpt-5.6-luna" || role.OMP.ThinkingLevel != "xhigh" {
261+
t.Fatalf("unexpected OMP options: %#v", role.OMP)
262+
}
210263
if filepath.Base(role.Source) != "reviewer.md" {
211264
t.Fatalf("unexpected source: %q", role.Source)
212265
}
213266
}
267+
268+
func TestCanonicalResearcherRendersCodexAtMax(t *testing.T) {
269+
repoRoot, err := filepath.Abs(filepath.Join("..", ".."))
270+
if err != nil {
271+
t.Fatal(err)
272+
}
273+
role, err := loadMarkdownAgentRole(filepath.Join(repoRoot, "agents", "researcher.md"))
274+
if err != nil {
275+
t.Fatal(err)
276+
}
277+
278+
got := renderCodexAgentRole(role)
279+
for _, want := range []string{
280+
`name = "researcher"`,
281+
`model = "gpt-5.6-luna"`,
282+
`model_reasoning_effort = "max"`,
283+
} {
284+
if !strings.Contains(got, want) {
285+
t.Fatalf("canonical researcher Codex role missing %q:\n%s", want, got)
286+
}
287+
}
288+
}
289+
290+
func TestCanonicalResearcherRendersOMPAtMaximum(t *testing.T) {
291+
repoRoot, err := filepath.Abs(filepath.Join("..", ".."))
292+
if err != nil {
293+
t.Fatal(err)
294+
}
295+
role, err := loadMarkdownAgentRole(filepath.Join(repoRoot, "agents", "researcher.md"))
296+
if err != nil {
297+
t.Fatal(err)
298+
}
299+
300+
got := renderOMPAgentRole(role)
301+
for _, want := range []string{
302+
`name: "researcher"`,
303+
`- "gpt-5.6-luna"`,
304+
`thinking-level: "max"`,
305+
} {
306+
if !strings.Contains(got, want) {
307+
t.Fatalf("canonical researcher OMP role missing %q:\n%s", want, got)
308+
}
309+
}
310+
}
311+
214312
func TestLoadAgentRolesMarkdownRejectsMappingTools(t *testing.T) {
215313
repoRoot := t.TempDir()
216314
writeAgentsFixture(t, repoRoot, "invalid.md", `---

cmd/dotagents/omp_agent.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,20 @@ var ompToolMapping = map[string]string{
1414
}
1515

1616
func renderOMPAgentRole(role agentRole) string {
17+
model := strings.TrimSpace(role.OMP.Model)
18+
if model == "" {
19+
model = strings.TrimSpace(role.Model)
20+
}
21+
1722
var b strings.Builder
1823
b.WriteString("---\n")
1924
writeYAMLScalar(&b, "name", role.Name)
2025
writeYAMLScalar(&b, "description", role.Description)
21-
if model := strings.TrimSpace(role.Model); model != "" {
26+
if model != "" {
2227
b.WriteString("model:\n")
2328
writeYAMLListItem(&b, model)
2429
}
30+
writeYAMLScalar(&b, "thinking-level", role.OMP.ThinkingLevel)
2531
if tools := ompToolsFor(role.Tools); len(tools) > 0 {
2632
b.WriteString("tools:\n")
2733
for _, tool := range tools {

memory/README.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,39 @@ session start. It does not invoke memsearch or persist raw transcripts.
2828
The memsearch tier registers the indexed SessionStart, Stop, and SessionEnd
2929
pipeline. Configure its vault through `~/.agents/memsearch.conf`.
3030

31+
## Dream review
32+
33+
Run the dream pass manually to find exact repeated preferences or corrections,
34+
duplicate session records, and conflicting copies:
35+
36+
```bash
37+
python3 ~/.agents/memory/lib/basic_memory.py dream \
38+
--output "$KNOWLEDGE_DIR/reviews/memory-dream-2026-08-19.json"
39+
```
40+
41+
The output is a deterministic, review-only JSON artifact. The command reads
42+
complete marker-delimited records under `sessions/` plus exact duplicate facts
43+
in the legacy `sessions/knowledge.md` export. It does not scan
44+
`profile/USER.md`, edit canonical memory, archive records, or delete anything.
45+
The output path must be new and live under `$KNOWLEDGE_DIR/reviews/`.
46+
47+
Candidates are deliberately conservative:
48+
49+
- Repeated preferences and corrections require the same explicit statement in
50+
at least two distinct session IDs.
51+
- A stale duplicate requires an identical session block (ignoring trailing
52+
whitespace) outside its unique UTC-dated canonical file.
53+
- Legacy cleanup reports only exact facts repeated across distinct `## Sync`
54+
records; it does not infer staleness from age.
55+
- Reused session IDs with differing content are reported as conflicts, without
56+
choosing a survivor.
57+
- Assistant output, truncated statements, incomplete records, fuzzy semantic
58+
matches, and age alone never produce candidates.
59+
60+
Review the source coordinates and candidate IDs before making a separate,
61+
explicit edit to canonical memory. There is intentionally no apply or delete
62+
mode.
63+
3164
## Canonical paths
3265

3366
The knowledge vault path is set in `~/.agents/memsearch.conf` as `KNOWLEDGE_DIR`.

0 commit comments

Comments
 (0)