lcode-cli 0.6.1__tar.gz → 0.7.0__tar.gz
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.
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/CHANGELOG.md +16 -1
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/PKG-INFO +8 -1
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/README.md +7 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/docs/configuration.md +3 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/docs/how-it-works.md +5 -3
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/docs/index.md +7 -0
- lcode_cli-0.7.0/docs/memory.md +128 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/docs/quickstart.md +2 -1
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/docs/usage.md +7 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/mkdocs.yml +1 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/pyproject.toml +1 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/src/lcode/__init__.py +1 -1
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/src/lcode/agent.py +26 -4
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/src/lcode/cli.py +10 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/src/lcode/config.py +8 -0
- lcode_cli-0.7.0/src/lcode/memory.py +517 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/src/lcode/permissions.py +8 -3
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/src/lcode/repl.py +137 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/src/lcode/tools.py +38 -1
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/tests/conftest.py +3 -1
- lcode_cli-0.7.0/tests/test_memory.py +341 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/uv.lock +23 -23
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/.editorconfig +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/.github/CODEOWNERS +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/.github/ISSUE_TEMPLATE/model_request.yml +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/.github/dependabot.yml +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/.github/workflows/ci.yml +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/.github/workflows/docs.yml +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/.github/workflows/release.yml +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/.gitignore +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/.pre-commit-config.yaml +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/AGENTS.md +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/CODE_OF_CONDUCT.md +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/CONTRIBUTING.md +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/LICENSE +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/SECURITY.md +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/docs/assets/demo.svg +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/docs/assets/extra.css +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/docs/assets/logo.svg +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/docs/assets/social-preview.png +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/docs/bench.md +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/docs/changelog.md +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/docs/contributing.md +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/docs/installation.md +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/docs/mcp.md +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/docs/models.md +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/docs/sandbox.md +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/docs/troubleshooting.md +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/install.sh +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/overrides/main.html +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/scripts/record_demo.py +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/scripts/social-preview.html +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/src/lcode/__main__.py +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/src/lcode/bench.py +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/src/lcode/catalog.py +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/src/lcode/checkpoints.py +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/src/lcode/hardware.py +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/src/lcode/limits.py +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/src/lcode/mcp/__init__.py +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/src/lcode/mcp/auth.py +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/src/lcode/mcp/catalog.py +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/src/lcode/mcp/catalog.toml +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/src/lcode/mcp/client.py +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/src/lcode/mcp/commands.py +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/src/lcode/mcp/config.py +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/src/lcode/mcp/http.py +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/src/lcode/mcp/manager.py +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/src/lcode/mcp/protocol.py +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/src/lcode/mcp/stdio.py +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/src/lcode/models.toml +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/src/lcode/ollama.py +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/src/lcode/render.py +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/src/lcode/sandbox.py +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/src/lcode/sessions.py +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/src/lcode/vision.py +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/src/lcode/web.py +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/tests/fake_mcp_server.py +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/tests/test_agent.py +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/tests/test_bench.py +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/tests/test_catalog.py +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/tests/test_checkpoints.py +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/tests/test_cli.py +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/tests/test_config.py +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/tests/test_context_picker.py +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/tests/test_mcp.py +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/tests/test_permissions.py +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/tests/test_sandbox.py +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/tests/test_sessions.py +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/tests/test_tools.py +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/tests/test_vision.py +0 -0
- {lcode_cli-0.6.1 → lcode_cli-0.7.0}/tests/test_web.py +0 -0
|
@@ -6,6 +6,20 @@ All notable changes to lcode are documented here. The format follows
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.7.0] - 2026-10-05
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Memory across sessions: lcode keeps short notes that later sessions load, only key knowledge and
|
|
14
|
+
not whole conversations. The model saves them with its new `memory` tool (and updates or deletes
|
|
15
|
+
them), you add them with `/remember`, and when a session ends (on quit, `/clear` or before
|
|
16
|
+
compaction) one quick question asks the model what from it is worth remembering. Notes are
|
|
17
|
+
per repository, shared by its git worktrees, plus a few for every repository; they're plain
|
|
18
|
+
markdown files, managed with `/memory`. The index in the system prompt is capped at about 2,000
|
|
19
|
+
tokens. Notes that look like they contain secrets are never saved. The `memory` setting is `ask`
|
|
20
|
+
(confirm each note, the default), `auto` or `off`; `--no-memory` turns it off for a session.
|
|
21
|
+
See [Memory](https://nasser1941.github.io/lcode/memory/).
|
|
22
|
+
|
|
9
23
|
## [0.6.1] - 2026-10-01
|
|
10
24
|
|
|
11
25
|
### Fixed
|
|
@@ -211,7 +225,8 @@ First public release.
|
|
|
211
225
|
- `AGENTS.md` project instructions and `/init` to generate them.
|
|
212
226
|
- One-line installer for Linux and macOS.
|
|
213
227
|
|
|
214
|
-
[Unreleased]: https://github.com/nasser1941/lcode/compare/v0.
|
|
228
|
+
[Unreleased]: https://github.com/nasser1941/lcode/compare/v0.7.0...HEAD
|
|
229
|
+
[0.7.0]: https://github.com/nasser1941/lcode/compare/v0.6.1...v0.7.0
|
|
215
230
|
[0.6.1]: https://github.com/nasser1941/lcode/compare/v0.6.0...v0.6.1
|
|
216
231
|
[0.6.0]: https://github.com/nasser1941/lcode/compare/v0.5.0...v0.6.0
|
|
217
232
|
[0.5.0]: https://github.com/nasser1941/lcode/compare/v0.4.1...v0.5.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: lcode-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: A local-first terminal coding agent powered by open-weight models on your own GPU or Mac, via Ollama.
|
|
5
5
|
Project-URL: Homepage, https://nasser1941.github.io/lcode/
|
|
6
6
|
Project-URL: Documentation, https://nasser1941.github.io/lcode/
|
|
@@ -82,6 +82,9 @@ through [Ollama](https://ollama.com), and when it needs current information it c
|
|
|
82
82
|
Podman) that only sees your project, without network access unless you allow it.
|
|
83
83
|
- **Undo.** lcode saves a checkpoint before the model changes files; `/undo` takes back the last
|
|
84
84
|
request's edits, new files and shell-command changes, without touching your git history.
|
|
85
|
+
- **Remembers what matters.** Corrections, decisions and where things live carry over to the next
|
|
86
|
+
session as short notes, saved by the model, by you (`/remember`) or by a quick check at the end
|
|
87
|
+
of a session. Not whole conversations, and never secrets.
|
|
85
88
|
- **Bring your own model.** Eight curated open-weight models, all tested end to end, or any Ollama model
|
|
86
89
|
with tool calling.
|
|
87
90
|
- **MCP servers, ready to go.** Connect Jira and Confluence, GitHub, AWS, Google Drive, Grafana,
|
|
@@ -189,6 +192,10 @@ fills up. Details: [How it works](https://nasser1941.github.io/lcode/how-it-work
|
|
|
189
192
|
|
|
190
193
|
See the pinned [Roadmap issue](https://github.com/nasser1941/lcode/issues/10). Next up:
|
|
191
194
|
|
|
195
|
+
- [Parallel subagents](https://github.com/nasser1941/lcode/issues/40) with their own context
|
|
196
|
+
- [Plan mode](https://github.com/nasser1941/lcode/issues/41) and [custom commands and skills](https://github.com/nasser1941/lcode/issues/42)
|
|
197
|
+
- [Ready-made MCP for Metabase, Encord and Valohai](https://github.com/nasser1941/lcode/issues/53)
|
|
198
|
+
- Then: [smarter context for small windows](https://github.com/nasser1941/lcode/issues/46), [language servers](https://github.com/nasser1941/lcode/issues/44), [a repository map with local embeddings](https://github.com/nasser1941/lcode/issues/45), [hooks](https://github.com/nasser1941/lcode/issues/43)
|
|
192
199
|
- More models in the catalog, and test reports from your hardware ([report one](https://github.com/nasser1941/lcode/issues/new?template=model_request.yml))
|
|
193
200
|
|
|
194
201
|
## Contributing
|
|
@@ -48,6 +48,9 @@ through [Ollama](https://ollama.com), and when it needs current information it c
|
|
|
48
48
|
Podman) that only sees your project, without network access unless you allow it.
|
|
49
49
|
- **Undo.** lcode saves a checkpoint before the model changes files; `/undo` takes back the last
|
|
50
50
|
request's edits, new files and shell-command changes, without touching your git history.
|
|
51
|
+
- **Remembers what matters.** Corrections, decisions and where things live carry over to the next
|
|
52
|
+
session as short notes, saved by the model, by you (`/remember`) or by a quick check at the end
|
|
53
|
+
of a session. Not whole conversations, and never secrets.
|
|
51
54
|
- **Bring your own model.** Eight curated open-weight models, all tested end to end, or any Ollama model
|
|
52
55
|
with tool calling.
|
|
53
56
|
- **MCP servers, ready to go.** Connect Jira and Confluence, GitHub, AWS, Google Drive, Grafana,
|
|
@@ -155,6 +158,10 @@ fills up. Details: [How it works](https://nasser1941.github.io/lcode/how-it-work
|
|
|
155
158
|
|
|
156
159
|
See the pinned [Roadmap issue](https://github.com/nasser1941/lcode/issues/10). Next up:
|
|
157
160
|
|
|
161
|
+
- [Parallel subagents](https://github.com/nasser1941/lcode/issues/40) with their own context
|
|
162
|
+
- [Plan mode](https://github.com/nasser1941/lcode/issues/41) and [custom commands and skills](https://github.com/nasser1941/lcode/issues/42)
|
|
163
|
+
- [Ready-made MCP for Metabase, Encord and Valohai](https://github.com/nasser1941/lcode/issues/53)
|
|
164
|
+
- Then: [smarter context for small windows](https://github.com/nasser1941/lcode/issues/46), [language servers](https://github.com/nasser1941/lcode/issues/44), [a repository map with local embeddings](https://github.com/nasser1941/lcode/issues/45), [hooks](https://github.com/nasser1941/lcode/issues/43)
|
|
158
165
|
- More models in the catalog, and test reports from your hardware ([report one](https://github.com/nasser1941/lcode/issues/new?template=model_request.yml))
|
|
159
166
|
|
|
160
167
|
## Contributing
|
|
@@ -30,6 +30,7 @@ lcode config path # print the file location
|
|
|
30
30
|
| `sandbox_network` | `false` | Let commands in the sandbox use the network |
|
|
31
31
|
| `vision_model` | `auto` | The model that [looks at images](usage.md#images): `auto`, `off` or an Ollama model that can see |
|
|
32
32
|
| `mcp_tools` | `auto` | How [MCP](mcp.md#context) tool definitions reach the model: `auto`, `direct` or `search` (on demand) |
|
|
33
|
+
| `memory` | `ask` | [Notes that carry over](memory.md) to later sessions: `ask` (confirm each), `auto` or `off` |
|
|
33
34
|
| `checkpoints` | `true` | Save a checkpoint before the model changes files, so [`/undo`](usage.md#undo-and-checkpoints) can restore them |
|
|
34
35
|
|
|
35
36
|
Example file:
|
|
@@ -55,6 +56,7 @@ Environment variables override the file, which is useful for one-off runs and CI
|
|
|
55
56
|
| `OLLAMA_HOST` | `ollama_host` (same variable the Ollama CLI uses) |
|
|
56
57
|
| `LCODE_WEB` | `web` |
|
|
57
58
|
| `LCODE_SANDBOX` | `sandbox` |
|
|
59
|
+
| `LCODE_MEMORY` | `memory` |
|
|
58
60
|
| `OLLAMA_API_KEY`, `BRAVE_API_KEY`, `TAVILY_API_KEY` | API key for that [search provider](usage.md#web-search) (never stored in the config file) |
|
|
59
61
|
| `LCODE_HOME` | where sessions and prompt history are stored |
|
|
60
62
|
|
|
@@ -69,6 +71,7 @@ Environment variables override the file, which is useful for one-off runs and CI
|
|
|
69
71
|
| `~/.local/state/lcode/mcp-auth/` | Sign-in tokens for remote MCP servers (readable only by you) |
|
|
70
72
|
| `~/.local/state/lcode/mcp-logs/` | Error output of local MCP servers |
|
|
71
73
|
| `~/.local/state/lcode/checkpoints/` | Checkpoints for `/undo` (copies of your project's files; deleted after 14 days) |
|
|
74
|
+
| `~/.local/state/lcode/memory/` | [Memory](memory.md) notes: `user/` for every repository, `projects/<repository>/` for each one |
|
|
72
75
|
| `~/.local/state/lcode/limits.json` | Context sizes that ran out of GPU memory on this machine (safe to delete) |
|
|
73
76
|
|
|
74
77
|
Sessions contain everything the model read, including file contents. Delete the folder to clear them.
|
|
@@ -18,7 +18,7 @@ flowchart LR
|
|
|
18
18
|
## The agent loop
|
|
19
19
|
|
|
20
20
|
1. Your message goes to the model along with a system prompt (environment, repository layout,
|
|
21
|
-
`AGENTS.md
|
|
21
|
+
`AGENTS.md`, the index of [memory notes](memory.md)) and the JSON schemas of the tools.
|
|
22
22
|
2. The response streams back. Reasoning shows as a spinner (or text with `--show-thinking`); the
|
|
23
23
|
answer is rendered as markdown, block by block.
|
|
24
24
|
3. If the model called tools, lcode runs them, asking for permission where needed, and appends the
|
|
@@ -34,8 +34,9 @@ didn't match).
|
|
|
34
34
|
Ollama keeps the processed conversation in its KV cache, so each turn only processes the new tokens.
|
|
35
35
|
lcode tracks how full the window is from Ollama's token counts. At 85% it asks the model to write a
|
|
36
36
|
summary of the conversation (goals, findings, files changed, next steps) and continues from that
|
|
37
|
-
summary.
|
|
38
|
-
|
|
37
|
+
summary. Just before that, it asks whether anything from the conversation is worth
|
|
38
|
+
[remembering](memory.md), since the summary leaves details out. Tool outputs are capped at 30,000
|
|
39
|
+
characters (head and tail kept) so one noisy command can't flood the window.
|
|
39
40
|
|
|
40
41
|
## Memory sizing
|
|
41
42
|
|
|
@@ -78,6 +79,7 @@ the Ollama server you configure, it only contacts the web when the model searche
|
|
|
78
79
|
| `mcp/` | MCP client: stdio and HTTP transports, OAuth sign-in, server settings, the catalog, `lcode mcp` |
|
|
79
80
|
| `bench.py` | `lcode bench`: the benchmark tasks, their checks and the reports |
|
|
80
81
|
| `vision.py` | Looking at images with a model that can see |
|
|
82
|
+
| `memory.py` | Notes that carry over between sessions, and the end-of-session check |
|
|
81
83
|
| `sandbox.py` | The optional container for shell commands |
|
|
82
84
|
| `checkpoints.py` | Snapshots before the model changes files, for `/undo` and `/rewind` |
|
|
83
85
|
| `catalog.py`, `models.toml` | Model catalog and memory estimates |
|
|
@@ -69,6 +69,13 @@ until the task is done, asking before it changes anything.
|
|
|
69
69
|
Every edit is shown as a diff and every non-read-only command needs your approval, unless you
|
|
70
70
|
choose otherwise. `/undo` takes back a request's file changes.
|
|
71
71
|
|
|
72
|
+
- :material-brain:{ .lg .middle } **Remembers what matters**
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
Your corrections, decisions and where things live carry over to the next session as short
|
|
77
|
+
[notes](memory.md), never whole conversations or secrets.
|
|
78
|
+
|
|
72
79
|
- :material-connection:{ .lg .middle } **MCP servers**
|
|
73
80
|
|
|
74
81
|
---
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# Memory
|
|
2
|
+
|
|
3
|
+
Every session used to start from zero. Whatever you and the model worked out was lost unless
|
|
4
|
+
someone wrote it into `AGENTS.md`: the test command that actually works, "we use pnpm, not npm",
|
|
5
|
+
that the staging database needs the VPN, a correction you had to give twice.
|
|
6
|
+
|
|
7
|
+
lcode now keeps short **notes** that later sessions load: only key knowledge, not whole
|
|
8
|
+
conversations. A note is one fact, in one line, with optional details.
|
|
9
|
+
|
|
10
|
+
```text
|
|
11
|
+
❯ /memory
|
|
12
|
+
Memory
|
|
13
|
+
# For Type Note Updated
|
|
14
|
+
1 every repo feedback Prefers short answers without a summary at the end 2026-10-03
|
|
15
|
+
2 this repo project Run the tests with `uv run pytest -q`; plain pyte… 2026-10-05
|
|
16
|
+
3 this repo reference Staging logs are in Grafana, dashboard "api-prod" 2026-10-04
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## What gets saved
|
|
20
|
+
|
|
21
|
+
Only what a future session needs and can't find in the code, the git history or `AGENTS.md`. There
|
|
22
|
+
are four kinds:
|
|
23
|
+
|
|
24
|
+
| Type | For | Example |
|
|
25
|
+
|---|---|---|
|
|
26
|
+
| `feedback` | Your corrections and preferences | "Use pnpm, not npm" |
|
|
27
|
+
| `project` | Decisions, constraints, ongoing work | "The staging database needs the VPN" |
|
|
28
|
+
| `reference` | Where things live outside the repository | "Bugs are tracked in Linear, project API" |
|
|
29
|
+
| `user` | Who you are and how you like to work | "Prefers short answers" |
|
|
30
|
+
|
|
31
|
+
Never saved: code structure, file paths, what changed in a session (git has that), temporary
|
|
32
|
+
state, and **secrets**. lcode refuses any note that looks like it contains a password, API key,
|
|
33
|
+
token, private key or a URL with a password in it, whoever writes it. Write where the secret lives
|
|
34
|
+
instead ("staging credentials are in 1Password, vault Dev").
|
|
35
|
+
|
|
36
|
+
## How notes get saved
|
|
37
|
+
|
|
38
|
+
- **The model** saves a note with its `memory` tool when it learns something worth keeping, for
|
|
39
|
+
example after you correct it. It can also update a note (same name) or delete one that turned out
|
|
40
|
+
wrong.
|
|
41
|
+
- **You** save one with `/remember`:
|
|
42
|
+
|
|
43
|
+
```text
|
|
44
|
+
❯ /remember the staging database needs the VPN
|
|
45
|
+
❯ /remember feedback: don't add docstrings to private functions
|
|
46
|
+
❯ /remember -g user: I'm a backend developer; explain frontend code in more detail
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`-g` saves it for every repository. Start with `feedback:`, `reference:` or `user:` to set the
|
|
50
|
+
type (the default is `project`).
|
|
51
|
+
|
|
52
|
+
- **At the end of a session**, because local models rarely save anything on their own, lcode
|
|
53
|
+
asks the model one short question: is anything from this session worth remembering? It happens
|
|
54
|
+
when you quit, on `/clear`, and before the conversation is compacted (the summary leaves details
|
|
55
|
+
out). The model proposes at most three notes:
|
|
56
|
+
|
|
57
|
+
```text
|
|
58
|
+
╭─ Worth remembering? ────────────────────────────────────────────────────────╮
|
|
59
|
+
│ 1 [feedback] Use pnpm, not npm (this repository) │
|
|
60
|
+
│ 2 [project] Integration tests need `docker compose up db` first (this │
|
|
61
|
+
│ repository) │
|
|
62
|
+
╰───────────────────────────────────────────────────────────────────────────────╯
|
|
63
|
+
Save them? [y]es / numbers to keep, e.g. 1,3 / [n]o: 1
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The question is quick: it reuses the conversation Ollama already has in memory, so the model
|
|
67
|
+
only writes a few lines. Short sessions (one request and little work) are skipped, and Ctrl+C
|
|
68
|
+
skips it too.
|
|
69
|
+
|
|
70
|
+
## Asking first: the `memory` setting
|
|
71
|
+
|
|
72
|
+
| `memory` | |
|
|
73
|
+
|---|---|
|
|
74
|
+
| `ask` (default) | Every note the model saves, and the end-of-session proposals, need your OK. Answer `a` once to allow the model's notes for the rest of the session. This holds in `yolo` mode too. |
|
|
75
|
+
| `auto` | Notes are saved without asking; lcode prints each one. |
|
|
76
|
+
| `off` | No notes are loaded or saved, and the model has no `memory` tool. |
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
lcode config set memory auto
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
`lcode --no-memory` turns memory off for one session. `lcode -p` runs never ask the end-of-session
|
|
83
|
+
question.
|
|
84
|
+
|
|
85
|
+
## Managing notes
|
|
86
|
+
|
|
87
|
+
| Command | |
|
|
88
|
+
|---|---|
|
|
89
|
+
| `/memory` | List the notes for this repository and for every repository |
|
|
90
|
+
| `/memory show 2` | Show a note with its details (by number or name) |
|
|
91
|
+
| `/memory edit 2` | Open it in your editor (`$VISUAL`, `$EDITOR`, nano or vi) |
|
|
92
|
+
| `/memory delete 2` | Delete it |
|
|
93
|
+
| `/memory path` | Where the notes are stored |
|
|
94
|
+
|
|
95
|
+
Changes you make with `/memory` apply from the next session. Notes added with `/remember` are
|
|
96
|
+
also mentioned to the model right away.
|
|
97
|
+
|
|
98
|
+
## How it works
|
|
99
|
+
|
|
100
|
+
Notes are plain markdown files with a small header, one fact per file:
|
|
101
|
+
|
|
102
|
+
```markdown
|
|
103
|
+
---
|
|
104
|
+
name: test-command
|
|
105
|
+
type: project
|
|
106
|
+
description: Run the tests with `uv run pytest -q`; plain pytest misses the dev dependencies
|
|
107
|
+
modified: 2026-10-05
|
|
108
|
+
---
|
|
109
|
+
The dev dependencies are in a uv dependency group, which plain pytest doesn't see.
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
| Folder | Notes |
|
|
113
|
+
|---|---|
|
|
114
|
+
| `~/.local/state/lcode/memory/projects/<repository>/` | For one repository, shared by all its git worktrees and subfolders |
|
|
115
|
+
| `~/.local/state/lcode/memory/user/` | For every repository |
|
|
116
|
+
|
|
117
|
+
Each folder also has a `MEMORY.md` listing its notes, for reading; lcode regenerates it, so edit
|
|
118
|
+
the notes rather than that file. A folder that isn't in a git repository gets its own notes.
|
|
119
|
+
|
|
120
|
+
At the start of a session, the model gets a one-line index of the notes (description, name, date)
|
|
121
|
+
in its instructions. The index is capped at about 2,000 tokens, or 5% of a small context window;
|
|
122
|
+
when there are more notes, the newest are shown and the model can list the rest with its `memory`
|
|
123
|
+
tool, which also reads a note's details. The dates let stale facts stand out, and the model is told
|
|
124
|
+
to trust what it finds in the code over an old note, and to fix the note.
|
|
125
|
+
|
|
126
|
+
Saving under an existing name replaces that note. A new note that says nearly the same as another
|
|
127
|
+
one is not saved twice: the model is told to update the existing note instead, and
|
|
128
|
+
end-of-session proposals that only restate a note are dropped.
|
|
@@ -12,12 +12,13 @@ lcode
|
|
|
12
12
|
|
|
13
13
|
```text
|
|
14
14
|
╭──────────────────────────────────────────────╮
|
|
15
|
-
│ lcode v0.
|
|
15
|
+
│ lcode v0.7.0 — local coding agent │
|
|
16
16
|
│ │
|
|
17
17
|
│ model lcode-qwen3.6-35b │
|
|
18
18
|
│ context 256K tokens │
|
|
19
19
|
│ cwd /home/you/code/my-project │
|
|
20
20
|
│ mode ask (Shift+Tab to cycle) │
|
|
21
|
+
│ memory ask · no notes yet (/remember) │
|
|
21
22
|
╰──────────────────────────────────────────────╯
|
|
22
23
|
❯
|
|
23
24
|
```
|
|
@@ -25,6 +25,7 @@ lcode mcp [catalog|add|list] # connect MCP servers: Jira, GitHub, AWS, databases
|
|
|
25
25
|
| `--no-think` | Turn off the model's reasoning: faster, less accurate |
|
|
26
26
|
| `--no-web` | No web search or page fetching in this session |
|
|
27
27
|
| `--no-mcp` | Don't start [MCP servers](mcp.md) in this session |
|
|
28
|
+
| `--no-memory` | Don't load or save [memory notes](memory.md) in this session |
|
|
28
29
|
| `--sandbox` | Run shell commands in a container that only sees the project ([Sandbox](sandbox.md)) |
|
|
29
30
|
| `--show-thinking` | Print the model's reasoning as it streams |
|
|
30
31
|
| `-V, --version` | Print the version |
|
|
@@ -56,6 +57,8 @@ reasoning is on.
|
|
|
56
57
|
| `/undo` | Undo the file changes of the last request ([details](#undo-and-checkpoints)) |
|
|
57
58
|
| `/rewind [N]` | Go back to before request N: its files, and optionally the conversation |
|
|
58
59
|
| `/checkpoints` | List the requests that changed files, and which files |
|
|
60
|
+
| `/remember [-g] TEXT` | Save a [note](memory.md) for later sessions (`-g`: for every repository) |
|
|
61
|
+
| `/memory [show\|edit\|delete N]` | The [notes](memory.md) lcode remembers |
|
|
59
62
|
| `/sandbox [network on\|off]` | The [sandbox](sandbox.md) for shell commands |
|
|
60
63
|
| `/mcp [tools\|login\|restart NAME]` | [MCP servers](mcp.md), their status and tools |
|
|
61
64
|
| `/compact [focus]` | Summarize the conversation to free context |
|
|
@@ -225,6 +228,10 @@ At startup lcode reads `AGENTS.md` (or `LCODE.md`, or `CLAUDE.md`) from the work
|
|
|
225
228
|
adds it to the model's instructions. Use it for build and test commands, architecture notes and
|
|
226
229
|
conventions. `/init` writes a first version for you.
|
|
227
230
|
|
|
231
|
+
What doesn't belong in `AGENTS.md` but should carry over, such as your corrections and preferences,
|
|
232
|
+
goes into lcode's [memory](memory.md): short notes the model, you (`/remember`) or a short check at
|
|
233
|
+
the end of a session save, and that every later session in the repository loads.
|
|
234
|
+
|
|
228
235
|
## Sessions
|
|
229
236
|
|
|
230
237
|
Every conversation is saved after each request to `~/.local/state/lcode/sessions/`, so you can
|
|
@@ -21,6 +21,7 @@ from rich.panel import Panel
|
|
|
21
21
|
from rich.text import Text
|
|
22
22
|
|
|
23
23
|
from lcode import catalog, limits, sessions, vision, web
|
|
24
|
+
from lcode import memory as memory_notes
|
|
24
25
|
from lcode.checkpoints import Checkpoints
|
|
25
26
|
from lcode.config import format_tokens
|
|
26
27
|
from lcode.mcp import McpManager
|
|
@@ -29,6 +30,7 @@ from lcode.permissions import Permissions
|
|
|
29
30
|
from lcode.render import MarkdownStreamer
|
|
30
31
|
from lcode.sandbox import Sandbox, SandboxError, project_root
|
|
31
32
|
from lcode.tools import (
|
|
33
|
+
MEMORY_SCHEMA,
|
|
32
34
|
SCHEMAS,
|
|
33
35
|
VIEW_IMAGE_SCHEMA,
|
|
34
36
|
WEB_FETCH_SCHEMA,
|
|
@@ -74,7 +76,7 @@ SYSTEM_PROMPT = """You are lcode, an autonomous software-engineering agent runni
|
|
|
74
76
|
|
|
75
77
|
# Top-level layout of the working directory
|
|
76
78
|
{tree}
|
|
77
|
-
{web}{
|
|
79
|
+
{web}{project}{notes}"""
|
|
78
80
|
|
|
79
81
|
WEB_PROMPT = """
|
|
80
82
|
# Web access
|
|
@@ -148,6 +150,7 @@ class Settings:
|
|
|
148
150
|
sandbox_image: str | None = None
|
|
149
151
|
sandbox_network: bool = False
|
|
150
152
|
vision_model: str = "auto" # auto, off or an Ollama model that can see images
|
|
153
|
+
memory: str = "off" # off, ask or auto: notes that carry over to later sessions (lcode.memory)
|
|
151
154
|
|
|
152
155
|
|
|
153
156
|
class Agent:
|
|
@@ -170,6 +173,9 @@ class Agent:
|
|
|
170
173
|
self._vision: str | bool | None = False # the model that looks at images; False = not decided yet
|
|
171
174
|
self.session_name = ""
|
|
172
175
|
self.session_title = ""
|
|
176
|
+
self.interactive = True # False for `lcode -p`: no end-of-session questions
|
|
177
|
+
self.reflected = 0 # messages before this index were already checked for notes to remember
|
|
178
|
+
self._memory: memory_notes.Memory | None = None
|
|
173
179
|
self.ctx_used = 0
|
|
174
180
|
self.last_speed = 0.0
|
|
175
181
|
self.usage = {"requests": 0, "prompt_tokens": 0, "prompt_ns": 0, "output_tokens": 0, "output_ns": 0}
|
|
@@ -182,11 +188,11 @@ class Agent:
|
|
|
182
188
|
return dt.datetime.now().strftime("%Y%m%d-%H%M%S-") + uuid.uuid4().hex[:6]
|
|
183
189
|
|
|
184
190
|
def system_prompt(self) -> str:
|
|
185
|
-
|
|
191
|
+
project = ""
|
|
186
192
|
for name in PROJECT_FILES:
|
|
187
193
|
f = self.cwd / name
|
|
188
194
|
if f.is_file():
|
|
189
|
-
|
|
195
|
+
project = f"\n# Project instructions ({name})\n{truncate(f.read_text(errors='replace'), 20_000)}\n"
|
|
190
196
|
break
|
|
191
197
|
return SYSTEM_PROMPT.format(
|
|
192
198
|
model=self.settings.model,
|
|
@@ -196,10 +202,17 @@ class Agent:
|
|
|
196
202
|
date=dt.date.today().isoformat(),
|
|
197
203
|
git=git_info(self.cwd),
|
|
198
204
|
tree=tree(self.cwd, depth=1, limit=120),
|
|
199
|
-
|
|
205
|
+
project=project,
|
|
206
|
+
notes=self.memory().prompt(self.settings.context) if self.settings.memory != "off" else "",
|
|
200
207
|
web=self.web_prompt(),
|
|
201
208
|
)
|
|
202
209
|
|
|
210
|
+
def memory(self) -> memory_notes.Memory:
|
|
211
|
+
"""The notes for the current folder's repository, plus the user's own."""
|
|
212
|
+
if self._memory is None or self._memory.cwd != self.cwd:
|
|
213
|
+
self._memory = memory_notes.Memory(self.cwd)
|
|
214
|
+
return self._memory
|
|
215
|
+
|
|
203
216
|
def search_backend(self) -> str | None:
|
|
204
217
|
if self.settings.web == "off":
|
|
205
218
|
return None
|
|
@@ -211,6 +224,8 @@ class Agent:
|
|
|
211
224
|
schemas += [*([WEB_SEARCH_SCHEMA] if self.search_backend() else []), WEB_FETCH_SCHEMA]
|
|
212
225
|
if self.vision_model():
|
|
213
226
|
schemas.append(VIEW_IMAGE_SCHEMA)
|
|
227
|
+
if self.settings.memory != "off":
|
|
228
|
+
schemas.append(MEMORY_SCHEMA)
|
|
214
229
|
if self.mcp:
|
|
215
230
|
schemas += self.mcp.schemas(self.settings.context)
|
|
216
231
|
return schemas
|
|
@@ -301,6 +316,7 @@ class Agent:
|
|
|
301
316
|
self.messages = [{"role": "system", "content": self.system_prompt()}]
|
|
302
317
|
self._mcp_prompt = ""
|
|
303
318
|
self.tools.read_mtimes.clear()
|
|
319
|
+
self.reflected = len(self.messages)
|
|
304
320
|
self.ctx_used = len(self.messages[0]["content"]) // 3
|
|
305
321
|
|
|
306
322
|
def session_file(self) -> Path:
|
|
@@ -357,6 +373,7 @@ class Agent:
|
|
|
357
373
|
self.session_name = data.get("name", "")
|
|
358
374
|
self.session_title = data.get("title") or sessions.title_from(self.messages)
|
|
359
375
|
self.tools.read_mtimes.clear() # files may have changed since; the model must read them again
|
|
376
|
+
self.reflected = len(self.messages) # checked for notes to remember when that session ended
|
|
360
377
|
self.ctx_used = sum(len(json.dumps(m)) for m in self.messages) // 3
|
|
361
378
|
return note
|
|
362
379
|
|
|
@@ -513,6 +530,8 @@ class Agent:
|
|
|
513
530
|
return f"glob({args.get('pattern', '')})"
|
|
514
531
|
if name == "list_dir":
|
|
515
532
|
return f"list_dir({args.get('path', '.')})"
|
|
533
|
+
if name == "memory":
|
|
534
|
+
return f"memory({' '.join(str(args.get(k) or '') for k in ('action', 'name')).strip()})"
|
|
516
535
|
if self.mcp and self.mcp.owns(name):
|
|
517
536
|
if name == "mcp_find_tools":
|
|
518
537
|
return f"mcp_find_tools({args.get('query', '')!r})"
|
|
@@ -646,6 +665,8 @@ class Agent:
|
|
|
646
665
|
def compact(self, focus: str = "") -> None:
|
|
647
666
|
if len(self.messages) <= 2:
|
|
648
667
|
return
|
|
668
|
+
if self.interactive:
|
|
669
|
+
memory_notes.reflect(self) # what the summary leaves out is gone for good
|
|
649
670
|
instructions = (
|
|
650
671
|
"Summarize this conversation so the work can continue in a fresh context. Include: the user's goals "
|
|
651
672
|
"and requests, key facts learned about the codebase (files, functions, paths with line numbers), "
|
|
@@ -664,6 +685,7 @@ class Agent:
|
|
|
664
685
|
{"role": "assistant", "content": "Got it — I have the context from the summary and will continue."},
|
|
665
686
|
]
|
|
666
687
|
self.ctx_used = sum(len(m["content"]) for m in self.messages) // 3
|
|
688
|
+
self.reflected = len(self.messages)
|
|
667
689
|
self.console.print(Panel(Markdown(summary), title="Compacted summary", border_style="blue"))
|
|
668
690
|
|
|
669
691
|
# -- settings changes
|
|
@@ -341,6 +341,13 @@ def cmd_doctor(args) -> None:
|
|
|
341
341
|
network = "network on" if cfg["sandbox_network"] else "no network"
|
|
342
342
|
line("Sandbox", problem or f"{cfg['sandbox']} · {network}", problem is None)
|
|
343
343
|
ok &= problem is None
|
|
344
|
+
if cfg["memory"] == "off":
|
|
345
|
+
line("Memory", "off (lcode config set memory ask)", True)
|
|
346
|
+
else:
|
|
347
|
+
from lcode.memory import Memory
|
|
348
|
+
|
|
349
|
+
project, user = Memory(Path.cwd()).counts()
|
|
350
|
+
line("Memory", f"{cfg['memory']} · {project} note(s) for this repository, {user} for every repository", True)
|
|
344
351
|
node = shutil.which("npx")
|
|
345
352
|
line("Node.js", "found" if node else "not found (some MCP servers need npx)", True if node else None)
|
|
346
353
|
sys.exit(0 if ok else 1)
|
|
@@ -509,8 +516,10 @@ def cmd_chat(args) -> None:
|
|
|
509
516
|
sandbox_image=cfg["sandbox_image"],
|
|
510
517
|
sandbox_network=cfg["sandbox_network"],
|
|
511
518
|
vision_model=cfg["vision_model"],
|
|
519
|
+
memory="off" if args.no_memory else cfg["memory"],
|
|
512
520
|
)
|
|
513
521
|
agent = Agent(ollama, settings, cwd, console=console)
|
|
522
|
+
agent.interactive = not args.prompt
|
|
514
523
|
if agent.sandbox:
|
|
515
524
|
# Never run commands unsandboxed when the user asked for a sandbox: stop here instead.
|
|
516
525
|
from lcode.sandbox import SandboxError
|
|
@@ -557,6 +566,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
557
566
|
parser.add_argument("--no-think", action="store_true", help="disable model reasoning (faster, less accurate)")
|
|
558
567
|
parser.add_argument("--no-web", action="store_true", help="don't let the model search or fetch web pages")
|
|
559
568
|
parser.add_argument("--no-mcp", action="store_true", help="don't start MCP servers in this session")
|
|
569
|
+
parser.add_argument("--no-memory", action="store_true", help="don't load or save memory notes in this session")
|
|
560
570
|
parser.add_argument("--sandbox", action="store_true", help="run shell commands in a container (Docker or Podman)")
|
|
561
571
|
parser.add_argument("--show-thinking", action="store_true", help="print the model's reasoning as it streams")
|
|
562
572
|
parser.add_argument("-V", "--version", action="version", version=f"lcode {__version__}")
|
|
@@ -26,6 +26,7 @@ WEB_MODES = ("on", "ask", "off")
|
|
|
26
26
|
SEARCH_BACKENDS = ("auto", "ollama", "brave", "tavily", "searxng")
|
|
27
27
|
MCP_TOOL_MODES = ("auto", "direct", "search")
|
|
28
28
|
SANDBOX_ENGINES = ("off", "docker", "podman")
|
|
29
|
+
MEMORY_MODES = ("off", "ask", "auto")
|
|
29
30
|
|
|
30
31
|
# key -> (default, type, help)
|
|
31
32
|
SETTINGS: dict[str, tuple[object, type, str]] = {
|
|
@@ -45,6 +46,7 @@ SETTINGS: dict[str, tuple[object, type, str]] = {
|
|
|
45
46
|
"sandbox_network": (False, bool, "let commands in the sandbox use the network"),
|
|
46
47
|
"vision_model": ("auto", str, "model that looks at images: auto | off | an Ollama model with vision"),
|
|
47
48
|
"mcp_tools": ("auto", str, "how MCP tools reach the model: auto | direct | search (on demand, saves context)"),
|
|
49
|
+
"memory": ("ask", str, "notes that carry over to later sessions: off | ask (confirm each) | auto"),
|
|
48
50
|
}
|
|
49
51
|
ENV_OVERRIDES = {
|
|
50
52
|
"LCODE_MODEL": "model",
|
|
@@ -54,6 +56,7 @@ ENV_OVERRIDES = {
|
|
|
54
56
|
"OLLAMA_HOST": "ollama_host",
|
|
55
57
|
"LCODE_WEB": "web",
|
|
56
58
|
"LCODE_SANDBOX": "sandbox",
|
|
59
|
+
"LCODE_MEMORY": "memory",
|
|
57
60
|
}
|
|
58
61
|
|
|
59
62
|
|
|
@@ -110,6 +113,11 @@ def coerce(key: str, value: object) -> object:
|
|
|
110
113
|
if value not in SANDBOX_ENGINES:
|
|
111
114
|
raise ConfigError(f"sandbox must be one of {', '.join(SANDBOX_ENGINES)}")
|
|
112
115
|
return value
|
|
116
|
+
if key == "memory":
|
|
117
|
+
value = {"on": "ask", "true": "ask", "false": "off", "no": "off"}.get(str(value).lower(), str(value).lower())
|
|
118
|
+
if value not in MEMORY_MODES:
|
|
119
|
+
raise ConfigError(f"memory must be one of {', '.join(MEMORY_MODES)}")
|
|
120
|
+
return value
|
|
113
121
|
if key == "mcp_tools" and value not in MCP_TOOL_MODES:
|
|
114
122
|
raise ConfigError(f"mcp_tools must be one of {', '.join(MCP_TOOL_MODES)}")
|
|
115
123
|
if key == "search_backend" and value not in SEARCH_BACKENDS:
|