personal memory agent
1# SPDX-License-Identifier: AGPL-3.0-only
2# Copyright (c) 2026 sol pbc
3
4"""Generate AGENTS.md from sol/identity.md using journal config values."""
5
6from __future__ import annotations
7
8import json
9import os
10from pathlib import Path
11from string import Template
12from typing import Any
13
14PROJECT_ROOT = Path(__file__).resolve().parent.parent
15DEFAULT_CONFIG_PATH = PROJECT_ROOT / "think" / "journal_default.json"
16SOURCE_PATH = PROJECT_ROOT / "sol" / "identity.md"
17OUTPUT_PATH = PROJECT_ROOT / "AGENTS.md"
18GENERATED_HEADER = "<!-- generated from sol/identity.md — do not edit directly -->\n\n"
19
20
21def _load_config() -> dict[str, Any]:
22 with open(DEFAULT_CONFIG_PATH, "r", encoding="utf-8") as f:
23 config = json.load(f)
24
25 journal_root = os.getenv("_SOLSTONE_JOURNAL_OVERRIDE")
26 if journal_root:
27 journal_path = Path(journal_root)
28 if not journal_path.is_absolute():
29 journal_path = PROJECT_ROOT / journal_path
30 else:
31 journal_path = PROJECT_ROOT / "journal"
32
33 config_path = journal_path / "config" / "journal.json"
34 if not config_path.exists():
35 return config
36
37 with open(config_path, "r", encoding="utf-8") as f:
38 return json.load(f)
39
40
41def _flatten_identity_to_template_vars(identity: dict[str, Any]) -> dict[str, str]:
42 template_vars: dict[str, str] = {}
43
44 for key, value in identity.items():
45 if isinstance(value, dict):
46 for subkey, subvalue in value.items():
47 var_name = f"{key}_{subkey}"
48 template_vars[var_name] = str(subvalue)
49 template_vars[var_name.capitalize()] = str(subvalue).capitalize()
50 elif isinstance(value, (str, int, float, bool)):
51 template_vars[key] = str(value)
52 template_vars[key.capitalize()] = str(value).capitalize()
53
54 return template_vars
55
56
57def _apply_human_defaults(
58 template_vars: dict[str, str], config: dict[str, Any]
59) -> None:
60 agent_name = str(config.get("agent", {}).get("name", "sol"))
61 template_vars["agent_name"] = agent_name
62 template_vars["Agent_name"] = agent_name.capitalize()
63
64 resolved_name = template_vars.get("name", "") or "your journal owner"
65 resolved_preferred = template_vars.get("preferred", "") or resolved_name
66
67 template_vars["name"] = resolved_name
68 template_vars["Name"] = resolved_name.capitalize()
69 template_vars["preferred"] = resolved_preferred
70 template_vars["Preferred"] = resolved_preferred.capitalize()
71
72 pronoun_defaults = {
73 "subject": "they",
74 "object": "them",
75 "possessive": "their",
76 "reflexive": "themselves",
77 }
78 for key, value in pronoun_defaults.items():
79 var_name = f"pronouns_{key}"
80 resolved = template_vars.get(var_name, "") or value
81 template_vars[var_name] = resolved
82 template_vars[var_name.capitalize()] = resolved.capitalize()
83
84
85def _load_template_body() -> str:
86 with open(SOURCE_PATH, "r", encoding="utf-8") as f:
87 lines = f.readlines()
88 for i, line in enumerate(lines):
89 if line.strip() == "}":
90 return "".join(lines[i + 1 :])
91 return "".join(lines)
92
93
94def main() -> None:
95 config = _load_config()
96 identity = config.get("identity", {})
97 template_vars = _flatten_identity_to_template_vars(identity)
98 _apply_human_defaults(template_vars, config)
99
100 content = _load_template_body()
101 rendered = Template(content).safe_substitute(template_vars)
102
103 with open(OUTPUT_PATH, "w", encoding="utf-8") as f:
104 f.write(GENERATED_HEADER)
105 f.write(rendered)
106
107 print("Generated AGENTS.md from sol/identity.md")
108
109
110if __name__ == "__main__":
111 main()