outrage 0.1.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.
Files changed (76) hide show
  1. outrage-0.1.0/.claude/RESUME.md +23 -0
  2. outrage-0.1.0/.claude/agents/rage-annotate.md +1 -0
  3. outrage-0.1.0/.claude/agents/rage-backfill.md +1 -0
  4. outrage-0.1.0/.claude/agents/rage-search.md +1 -0
  5. outrage-0.1.0/.claude/skills/rage/SKILL.md +155 -0
  6. outrage-0.1.0/.gitignore +29 -0
  7. outrage-0.1.0/.mcp.json +18 -0
  8. outrage-0.1.0/CLAUDE.md +25 -0
  9. outrage-0.1.0/LICENSE +9 -0
  10. outrage-0.1.0/PKG-INFO +130 -0
  11. outrage-0.1.0/README.md +107 -0
  12. outrage-0.1.0/design.md +731 -0
  13. outrage-0.1.0/docs/Makefile +26 -0
  14. outrage-0.1.0/docs/api/bulk.rst +4 -0
  15. outrage-0.1.0/docs/api/cli.rst +4 -0
  16. outrage-0.1.0/docs/api/config.rst +4 -0
  17. outrage-0.1.0/docs/api/errors.rst +5 -0
  18. outrage-0.1.0/docs/api/eventlog.rst +4 -0
  19. outrage-0.1.0/docs/api/index.rst +70 -0
  20. outrage-0.1.0/docs/api/install.rst +4 -0
  21. outrage-0.1.0/docs/api/keys.rst +4 -0
  22. outrage-0.1.0/docs/api/logread.rst +4 -0
  23. outrage-0.1.0/docs/api/maintenance.rst +4 -0
  24. outrage-0.1.0/docs/api/messages.rst +4 -0
  25. outrage-0.1.0/docs/api/mounts.rst +4 -0
  26. outrage-0.1.0/docs/api/server.rst +4 -0
  27. outrage-0.1.0/docs/api/store.rst +4 -0
  28. outrage-0.1.0/docs/api/store_parquet.rst +4 -0
  29. outrage-0.1.0/docs/api/store_sqlite.rst +4 -0
  30. outrage-0.1.0/docs/conf.py +65 -0
  31. outrage-0.1.0/docs/index.rst +36 -0
  32. outrage-0.1.0/environment.yml +5 -0
  33. outrage-0.1.0/implementation.md +474 -0
  34. outrage-0.1.0/pyproject.toml +55 -0
  35. outrage-0.1.0/requirements.md +174 -0
  36. outrage-0.1.0/src/outrage/__init__.py +3 -0
  37. outrage-0.1.0/src/outrage/__main__.py +6 -0
  38. outrage-0.1.0/src/outrage/agents/rage-annotate.md +108 -0
  39. outrage-0.1.0/src/outrage/agents/rage-backfill.md +78 -0
  40. outrage-0.1.0/src/outrage/agents/rage-search.md +137 -0
  41. outrage-0.1.0/src/outrage/bulk.py +593 -0
  42. outrage-0.1.0/src/outrage/cli.py +1358 -0
  43. outrage-0.1.0/src/outrage/config.py +296 -0
  44. outrage-0.1.0/src/outrage/errors.py +63 -0
  45. outrage-0.1.0/src/outrage/eventlog.py +291 -0
  46. outrage-0.1.0/src/outrage/hooks/README.md +35 -0
  47. outrage-0.1.0/src/outrage/hooks/settings.json +14 -0
  48. outrage-0.1.0/src/outrage/install.py +423 -0
  49. outrage-0.1.0/src/outrage/keys.py +672 -0
  50. outrage-0.1.0/src/outrage/logread.py +736 -0
  51. outrage-0.1.0/src/outrage/maintenance.py +335 -0
  52. outrage-0.1.0/src/outrage/messages.py +549 -0
  53. outrage-0.1.0/src/outrage/mounts.py +710 -0
  54. outrage-0.1.0/src/outrage/server.py +1419 -0
  55. outrage-0.1.0/src/outrage/store.py +1374 -0
  56. outrage-0.1.0/src/outrage/store_parquet.py +1303 -0
  57. outrage-0.1.0/src/outrage/store_sqlite.py +1313 -0
  58. outrage-0.1.0/tests/conftest.py +68 -0
  59. outrage-0.1.0/tests/test_agents.py +110 -0
  60. outrage-0.1.0/tests/test_bulk.py +452 -0
  61. outrage-0.1.0/tests/test_cli.py +1353 -0
  62. outrage-0.1.0/tests/test_config.py +407 -0
  63. outrage-0.1.0/tests/test_eventlog.py +246 -0
  64. outrage-0.1.0/tests/test_harness_delivery.py +182 -0
  65. outrage-0.1.0/tests/test_install.py +437 -0
  66. outrage-0.1.0/tests/test_keys.py +523 -0
  67. outrage-0.1.0/tests/test_logread.py +478 -0
  68. outrage-0.1.0/tests/test_messages.py +196 -0
  69. outrage-0.1.0/tests/test_mounts.py +1258 -0
  70. outrage-0.1.0/tests/test_public_api.py +151 -0
  71. outrage-0.1.0/tests/test_server.py +743 -0
  72. outrage-0.1.0/tests/test_skill.py +49 -0
  73. outrage-0.1.0/tests/test_store.py +1890 -0
  74. outrage-0.1.0/tests/test_store_parquet.py +901 -0
  75. outrage-0.1.0/tests/test_store_sqlite.py +357 -0
  76. outrage-0.1.0/tools/harness_delivery.py +534 -0
@@ -0,0 +1,23 @@
1
+ # Claude Code — resume checkpoint
2
+
3
+ Session: 440063bd-742b-41f8-8b80-0b5fe1852641
4
+ Written: 2026-08-21T11:20:15.294Z
5
+ Trigger: near-limit
6
+ Ref with your in-progress files: refs/claude/checkpoint-440063bd
7
+
8
+ ## To resume
9
+
10
+ claude --resume 440063bd-742b-41f8-8b80-0b5fe1852641
11
+
12
+ (or open Claude Code in this directory and run /resume)
13
+
14
+ ## Plan (from TodoWrite state)
15
+
16
+ No task list was active; see transcript via the resume command above.
17
+
18
+ ---
19
+
20
+ Don't want these changes? Resume this session (above), then run
21
+ `/rewind` to roll back the turn's tool edits (bash-made changes
22
+ excluded). refs/claude/checkpoint-440063bd holds a full snapshot until this session's
23
+ next checkpoint, or for up to ~2 weeks.
@@ -0,0 +1 @@
1
+ ../../src/outrage/agents/rage-annotate.md
@@ -0,0 +1 @@
1
+ ../../src/outrage/agents/rage-backfill.md
@@ -0,0 +1 @@
1
+ ../../src/outrage/agents/rage-search.md
@@ -0,0 +1,155 @@
1
+ ---
2
+ name: rage
3
+ description: Keep and recover working knowledge in this project's rage document store - notes, decisions, findings and task state that outlive one session. Use when starting or resuming work and the question is what earlier sessions established ("where were we", "what do we already know about X", "continue", "pick up where we left off"); when something worth keeping has just been settled (a decision and its reasoning, a finding that took effort to get, where the work got to); when asked to remember, store or save context; and before a session ends or its context is compacted.
4
+ ---
5
+
6
+ # The rage store
7
+
8
+ A document store for this project, reached through the `rage` MCP tools. The
9
+ tools carry the key grammar and the argument rules; this skill covers when to
10
+ use them and what to name things.
11
+
12
+ ## Start at `readme`
13
+
14
+ Every store's entry point is the document at `readme`: what this particular
15
+ store holds, and what to read before anything else. The server carries it at
16
+ the top of its instructions, so it arrives without being asked for and there is
17
+ normally nothing to do here. If it did not arrive, read it.
18
+
19
+ Three things follow from that. A readme long enough to be worth summarising is
20
+ too long - it routes, and the documents it points at explain. It has a hard cap
21
+ of a few hundred characters, because a client cuts the instructions at a length
22
+ it does not announce and the readme is what has to survive that cut; over the
23
+ cap the server reports its length instead of carrying it, which is the signal
24
+ to go and read it. And a store with no readme is asking for one: a session that
25
+ works out how the store is laid out, or what the next session should read
26
+ first, should write that there.
27
+
28
+ It is read once, when the server starts, so a readme stored now reaches the
29
+ next session rather than this one.
30
+
31
+ ## Survey before reading
32
+
33
+ One call shows what the store holds:
34
+
35
+ ```
36
+ get_documents(meta_name=["title"])
37
+ ```
38
+
39
+ That returns a title per document, not the documents, so it is cheap enough to
40
+ run at the start of any piece of work. Then read only what looks relevant, with
41
+ `retrieve_document`.
42
+
43
+ **Read `total`, not just what came back.** The result is a page: `returned`
44
+ titles out of `total` documents, with `next_cursor` set when there are more.
45
+ Twenty of twenty-two is a listing and you have seen the store; twenty of four
46
+ thousand is a sample, and treating it as the store is how a session concludes
47
+ confidently from a fraction of it. To continue, pass `next_cursor` back as
48
+ `after`. To look at one part instead of everything, pass `key`.
49
+
50
+ Check `without_meta` too. It reports how many documents carry no title, with a
51
+ few of their keys - the ones the survey cannot show at all, since it can only
52
+ report documents that have one. A survey that omits them silently is a survey
53
+ you cannot trust. It is always present, and it covers exactly the stretch of
54
+ the store this page covers, so paging the survey walks the gaps alongside the
55
+ titles instead of repeating a subtree-wide total on every page.
56
+
57
+ It is a count, not a listing, and carries no cursor: `keys_missing_meta` is
58
+ what enumerates them, and `rage-backfill` fills them in.
59
+
60
+ ## Key conventions
61
+
62
+ Three top level namespaces. Nothing enforces them; they exist so that a later
63
+ session can guess where to look.
64
+
65
+ `context/<n>/…` - one numbered thread of work. Do not invent the number: store
66
+ at `context/?/task` first and the result reports the key it allocated, then put
67
+ everything else in that thread alongside it.
68
+
69
+ * `context/<n>/task` - what is being attempted, in a few lines.
70
+ * `context/<n>/state` - where it got to, what is unfinished, what to do next.
71
+ * `context/<n>/design`, `…/findings`, `…/decisions` - whatever the work
72
+ produced that is worth keeping.
73
+
74
+ `project/reference/<topic>` - durable facts about the project itself, belonging
75
+ to no single thread: the environment, conventions, current status. Updated in
76
+ place as they change.
77
+
78
+ `notes/<path>` - notes about one file, keyed to mirror its path, as in
79
+ `notes/src/outrage/store.py`.
80
+
81
+ Pass `title` on every document. It is what the survey above can see, and a
82
+ document without one is findable only by someone who already knows its key.
83
+
84
+ Store a `!summary` as well for any document whose subject its title cannot
85
+ carry. `rage-search` screens on title *then* summary, so a document with a
86
+ title and no summary is judged on the title alone - and "Notes from the Tuesday
87
+ run" is an honest title that tells a reader nothing. Aim for about a fifth of
88
+ the document: a summary that approaches the length of its document costs as much
89
+ to screen as the document costs to read. `rage-backfill` fills these in across a subtree,
90
+ which is the right thing to run after adding several documents at once.
91
+
92
+ ## What is worth storing
93
+
94
+ Store what cost something to learn and that nothing else in the project
95
+ records:
96
+
97
+ * a decision, and why the alternative was rejected
98
+ * a finding from actually running the thing - an error, a behaviour, a
99
+ constraint in a dependency
100
+ * where the work got to, and what the next step is
101
+ * a correction: something believed at the start of the session and found false
102
+
103
+ Do not store what the repository already holds - what the code does, what the
104
+ diff changed, what the commit messages say. A note restating the code is a note
105
+ that will go stale and then mislead, and it costs a later session the read.
106
+
107
+ The test is whether someone could act on the document without the session that
108
+ wrote it.
109
+
110
+ ## Store as the work goes, not at the end
111
+
112
+ Store each thing once it is settled. A session that plans to write everything up
113
+ at the end stores nothing if it is cut short, and a full context window is
114
+ exactly the ending that arrives without warning.
115
+
116
+ Rewrite the same key rather than accumulating new ones: `context/1/state` should
117
+ be the current state, not the first of several. Storing overwrites, which is
118
+ what makes this cheap.
119
+
120
+ Keep `project/reference/…` current when the facts under it change. A stale
121
+ reference is worse than an absent one, because it is believed.
122
+
123
+ ## One document, one question
124
+
125
+ Rewriting a key keeps it current; it is not licence to let it grow. When there
126
+ is more to keep, prefer **adding a document beneath it** to extending the one
127
+ that is there. Sub-documents are the intended shape, and `?` allocates a key at
128
+ any depth - `context/1/findings/?` writes the next document under `findings` -
129
+ so adding one costs no naming decision and no thought about where it goes.
130
+
131
+ A document should be readable in one call. `retrieve_document` returns 8000
132
+ characters by default, so a longer document is read in slices, and a session
133
+ that judges it from the first slice judges it wrongly and silently.
134
+
135
+ Size is the symptom; scope is the cause. `project/reference/planned` reached
136
+ 11389 characters because it answered five questions at once - what CLI pieces
137
+ remain, what is open on the agents, what the log reader should do, how the
138
+ checkpoint prompt gets delivered, which files are stale. No title and no
139
+ summary can route a reader to the right part of a document like that, so it is
140
+ read whole or not at all.
141
+
142
+ Split along the questions, not by length. A key can hold content *and* have
143
+ keys beneath it, so the general document stays where it is and the detail goes
144
+ below: `project/reference/planned` is now a short index over `planned/cli`,
145
+ `planned/agents`, `planned/log-reader` and the rest. Anything already pointing
146
+ at the old key still lands somewhere useful, which is what makes a split safe
147
+ to do late - but adding as you go is cheaper than splitting afterwards.
148
+
149
+ ## Before the end
150
+
151
+ Whether the session is ending, being handed over, or about to be compacted:
152
+
153
+ * make sure `context/<n>/state` actually describes where things stand
154
+ * store anything settled that is still only in the conversation
155
+ * check the survey - anything under `without_meta` needs a title
@@ -0,0 +1,29 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .pytest_cache/
4
+ .ruff_cache/
5
+ *.egg-info/
6
+ build/
7
+ dist/
8
+ .venv/
9
+ .env
10
+ *.sqlite
11
+ *.sqlite3
12
+ *.db
13
+ .outrage/
14
+ .rage/
15
+ .DS_Store
16
+ .claude/settings.local.json
17
+ .claude/harness-canary/
18
+
19
+ # The SessionStart canary probes the client, not this project: it is
20
+ # registered in the untracked settings.local.json, its log above is ignored,
21
+ # and it ships nowhere. It stays on the machine that runs it.
22
+ .claude/hooks/sessionstart_canary.py
23
+ .claude/hooks/sessionstart-canary.sh
24
+ .claude/settings.json
25
+ .outrage-*/
26
+ .rage-*/
27
+
28
+ # Sphinx build output
29
+ docs/_build/
@@ -0,0 +1,18 @@
1
+ {
2
+ "mcpServers": {
3
+ "rage": {
4
+ "command": "/Users/john/miniforge3/envs/rage/bin/outrage-server",
5
+ "args": [
6
+ "--dir",
7
+ "/Users/john/Library/CloudStorage/OneDrive-Personal/Projects/Rage/.outrage",
8
+ "--mount",
9
+ "mount_test=mount-test.sqlite",
10
+ "--mount-ro",
11
+ "ref=ref.sqlite",
12
+ "--mount-ro",
13
+ "pyref=python.parquet",
14
+ "--log"
15
+ ]
16
+ }
17
+ }
18
+ }
@@ -0,0 +1,25 @@
1
+ # Outrage
2
+
3
+ This project keeps its working knowledge - decisions, findings, and where the
4
+ work got to - in its own rage document store, reached through the `rage` MCP
5
+ tools. That store is the record. **This file is only a pointer to it**, so that
6
+ nothing here can go stale against what the store says.
7
+
8
+ ## Start here
9
+
10
+ Read the document at key `project`. It is an index of what to read and *when it
11
+ applies*, and it routes to everything else. A survey by title
12
+ (`get_documents(meta_name=["title"])`) tells you what documents are about; the
13
+ `project` document tells you which one you need before you know to look.
14
+
15
+ ## Before snapshotting the store
16
+
17
+ Read `project/reference/snapshots` first. Copying `.outrage/store.sqlite` with `cp`
18
+ succeeds and produces a **silently stale** store, potentially a whole schema
19
+ version behind. Use `outrage backup`.
20
+
21
+ ## Conventions
22
+
23
+ The `rage` skill (`.claude/skills/rage/SKILL.md`) holds the key conventions and
24
+ what is worth storing. The tools themselves carry the key grammar and the
25
+ argument rules.
outrage-0.1.0/LICENSE ADDED
@@ -0,0 +1,9 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 John M Reynolds
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
outrage-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,130 @@
1
+ Metadata-Version: 2.5
2
+ Name: outrage
3
+ Version: 0.1.0
4
+ Summary: A key-addressed retrieval system for coding agents
5
+ Project-URL: Homepage, https://github.com/ZandevOxford/outrage
6
+ Project-URL: Issues, https://github.com/ZandevOxford/outrage/issues
7
+ Author-email: John M Reynolds <zandev@gmail.com>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Programming Language :: Python :: 3
12
+ Requires-Python: >=3.14
13
+ Requires-Dist: mcp>=1.2
14
+ Provides-Extra: dev
15
+ Requires-Dist: pytest>=8; extra == 'dev'
16
+ Requires-Dist: ruff>=0.6; extra == 'dev'
17
+ Provides-Extra: docs
18
+ Requires-Dist: furo; extra == 'docs'
19
+ Requires-Dist: sphinx>=8; extra == 'docs'
20
+ Provides-Extra: parquet
21
+ Requires-Dist: pyarrow>=18; extra == 'parquet'
22
+ Description-Content-Type: text/markdown
23
+
24
+ # Outrage
25
+
26
+ A simple retrieval system for coding agents: an MCP server, a data store, and
27
+ skills that let an agent keep notes, designs and task context in a store local
28
+ to the project it is working on.
29
+
30
+ Retrieval is by key rather than by similarity. Keys are hierarchical, slash
31
+ delimited strings such as `context/<guid>/design`, and any key may carry
32
+ metadata such as `context/<guid>/design/!title`. Since an agent knows the keys it
33
+ wrote, lexical addressing is enough, and the system stays deterministic and free
34
+ of external dependencies. Semantic search is a possible later addition, layered
35
+ on as metadata rather than as a change to the model.
36
+
37
+ Keys are not paths, but they are shaped like them, so a key can mirror one:
38
+ `notes/src/myfile.py` for notes about a source file. A key being written may
39
+ also use `?` in place of a segment - storing at `tmp/?` writes to `tmp/1`, and
40
+ the store reports the key it chose.
41
+
42
+ ## Purpose
43
+
44
+ To provide a hierarchical RAG (Retrieval-Augmented Generation) MCP store to use
45
+ with LLM based tools.
46
+
47
+ ## Features
48
+
49
+ * Simple to get started with - built in initialisation configures everything.
50
+ * Hierarchical store of data and metadata.
51
+ * LLM search - LLM subagents add summary metadata to documents, and LLM subagents can
52
+ search documents and metadata.
53
+ * Multiple data stores including parquet based stores for large-scale reference material.
54
+
55
+ ## Status
56
+
57
+ The store, the MCP server, the key handling, the skill and the command line tool
58
+ are implemented. The tool covers the store operations (`outrage get`, `set`,
59
+ `ls`, `dump`, `rm`), bulk export and import to a directory of files (`outrage
60
+ export`, `outrage import`), building a read-only parquet store (`outrage pack`),
61
+ the MCP configuration (`outrage config`), a verified backup (`outrage backup`),
62
+ the event log (`outrage log`), and setting a project up (`outrage init`). See
63
+ [implementation.md](implementation.md) for what is done and what is next.
64
+
65
+ `outrage init` is the way in: run it in a project and it registers the MCP
66
+ server, installs the `SessionStart` hook, and copies the skill and the agents
67
+ into `.claude/`. It writes only the entries outrage owns, leaves the rest of
68
+ those files alone, and is safe to re-run - which is how a project is repaired
69
+ after an upgrade or after the environment moves. `--dry-run` reports what it
70
+ would change without writing.
71
+
72
+ ## Documentation
73
+
74
+ * **[design.md](design.md)** - the design. Components, the key namespace and its
75
+ grammar, tool semantics, and the SQLite schema. Decisions that are deferred or
76
+ still open are recorded at the end.
77
+ * **[implementation.md](implementation.md)** - what is implemented so far, and
78
+ the planned build order.
79
+
80
+ ## Components
81
+
82
+ * **MCP server** - Python, stdio, for local use. Exposes the store as tools.
83
+ * **Data store** - a Python library, independent of MCP so that it can be tested
84
+ and reused on its own. One interface with two backends behind it, and which
85
+ one a store uses follows from its file's extension. **SQLite** is the
86
+ read-write default: a store accumulated a document at a time, which is what
87
+ session context and notes on a codebase are. **Parquet** is one columnar file,
88
+ written whole by `outrage pack` and read many times, for a reference base of
89
+ tens of thousands of documents - 11× smaller than the same corpus in SQLite,
90
+ and it seeks a range rather than scanning one. It refuses writes, which is the
91
+ storage rather than a setting.
92
+ * **Skill** - `src/outrage/skills/rage/SKILL.md`, initially for Claude Code,
93
+ covering when to store and retrieve and what key conventions to follow. It
94
+ ships inside the package so that an install carries it, and `outrage init`
95
+ copies it into a project. A `SessionStart` hook in `.claude/settings.json`,
96
+ installed by the same command, covers the moment a skill would not be reached
97
+ for on its own.
98
+ * **Event log** - an optional JSON lines record of the requests made and the
99
+ store accesses beneath them, for answering afterwards what a session actually
100
+ did. Off unless `outrage config --log` or `outrage-server --log` asks for it.
101
+ * **Backup** - `outrage backup` copies the database through SQLite and checks
102
+ what it wrote. In the library rather than the tool, because a store in WAL
103
+ mode keeps recent writes in a sidecar file and copying the `.sqlite` alone
104
+ yields a near-empty database that still opens cleanly.
105
+
106
+ ## Development
107
+
108
+ ```sh
109
+ conda env create -f environment.yml # or: conda create -n outrage -c conda-forge python=3.14
110
+ conda activate outrage
111
+ pip install -e ".[dev]"
112
+ pytest
113
+ ruff check . && ruff format --check .
114
+ ```
115
+
116
+ The stores live in a directory given to the server by `--dir` or `RAGE_DIR`,
117
+ defaulting to `./.outrage/` in the working directory, each as a file inside it:
118
+ `--root-mount FILE` names the one answering for everything (default
119
+ `store.sqlite`) and `--mount KEY=FILE` mounts another under a key. A store file
120
+ is always relative to the directory, so only `--dir` is a path. See
121
+ [design.md](design.md#store-location).
122
+
123
+ `outrage-server --log` records requests and store accesses as JSON lines, by
124
+ default in `log.jsonl` beside the store. It is off otherwise, since it records
125
+ document text. `--log-content none|excerpt|full` controls how much of that text
126
+ it keeps.
127
+
128
+ `.mcp.json` registers the server for this project. Its `command` is an absolute
129
+ path into the conda environment, so it is specific to the machine it was written
130
+ on; adjust it after creating the environment elsewhere.
@@ -0,0 +1,107 @@
1
+ # Outrage
2
+
3
+ A simple retrieval system for coding agents: an MCP server, a data store, and
4
+ skills that let an agent keep notes, designs and task context in a store local
5
+ to the project it is working on.
6
+
7
+ Retrieval is by key rather than by similarity. Keys are hierarchical, slash
8
+ delimited strings such as `context/<guid>/design`, and any key may carry
9
+ metadata such as `context/<guid>/design/!title`. Since an agent knows the keys it
10
+ wrote, lexical addressing is enough, and the system stays deterministic and free
11
+ of external dependencies. Semantic search is a possible later addition, layered
12
+ on as metadata rather than as a change to the model.
13
+
14
+ Keys are not paths, but they are shaped like them, so a key can mirror one:
15
+ `notes/src/myfile.py` for notes about a source file. A key being written may
16
+ also use `?` in place of a segment - storing at `tmp/?` writes to `tmp/1`, and
17
+ the store reports the key it chose.
18
+
19
+ ## Purpose
20
+
21
+ To provide a hierarchical RAG (Retrieval-Augmented Generation) MCP store to use
22
+ with LLM based tools.
23
+
24
+ ## Features
25
+
26
+ * Simple to get started with - built in initialisation configures everything.
27
+ * Hierarchical store of data and metadata.
28
+ * LLM search - LLM subagents add summary metadata to documents, and LLM subagents can
29
+ search documents and metadata.
30
+ * Multiple data stores including parquet based stores for large-scale reference material.
31
+
32
+ ## Status
33
+
34
+ The store, the MCP server, the key handling, the skill and the command line tool
35
+ are implemented. The tool covers the store operations (`outrage get`, `set`,
36
+ `ls`, `dump`, `rm`), bulk export and import to a directory of files (`outrage
37
+ export`, `outrage import`), building a read-only parquet store (`outrage pack`),
38
+ the MCP configuration (`outrage config`), a verified backup (`outrage backup`),
39
+ the event log (`outrage log`), and setting a project up (`outrage init`). See
40
+ [implementation.md](implementation.md) for what is done and what is next.
41
+
42
+ `outrage init` is the way in: run it in a project and it registers the MCP
43
+ server, installs the `SessionStart` hook, and copies the skill and the agents
44
+ into `.claude/`. It writes only the entries outrage owns, leaves the rest of
45
+ those files alone, and is safe to re-run - which is how a project is repaired
46
+ after an upgrade or after the environment moves. `--dry-run` reports what it
47
+ would change without writing.
48
+
49
+ ## Documentation
50
+
51
+ * **[design.md](design.md)** - the design. Components, the key namespace and its
52
+ grammar, tool semantics, and the SQLite schema. Decisions that are deferred or
53
+ still open are recorded at the end.
54
+ * **[implementation.md](implementation.md)** - what is implemented so far, and
55
+ the planned build order.
56
+
57
+ ## Components
58
+
59
+ * **MCP server** - Python, stdio, for local use. Exposes the store as tools.
60
+ * **Data store** - a Python library, independent of MCP so that it can be tested
61
+ and reused on its own. One interface with two backends behind it, and which
62
+ one a store uses follows from its file's extension. **SQLite** is the
63
+ read-write default: a store accumulated a document at a time, which is what
64
+ session context and notes on a codebase are. **Parquet** is one columnar file,
65
+ written whole by `outrage pack` and read many times, for a reference base of
66
+ tens of thousands of documents - 11× smaller than the same corpus in SQLite,
67
+ and it seeks a range rather than scanning one. It refuses writes, which is the
68
+ storage rather than a setting.
69
+ * **Skill** - `src/outrage/skills/rage/SKILL.md`, initially for Claude Code,
70
+ covering when to store and retrieve and what key conventions to follow. It
71
+ ships inside the package so that an install carries it, and `outrage init`
72
+ copies it into a project. A `SessionStart` hook in `.claude/settings.json`,
73
+ installed by the same command, covers the moment a skill would not be reached
74
+ for on its own.
75
+ * **Event log** - an optional JSON lines record of the requests made and the
76
+ store accesses beneath them, for answering afterwards what a session actually
77
+ did. Off unless `outrage config --log` or `outrage-server --log` asks for it.
78
+ * **Backup** - `outrage backup` copies the database through SQLite and checks
79
+ what it wrote. In the library rather than the tool, because a store in WAL
80
+ mode keeps recent writes in a sidecar file and copying the `.sqlite` alone
81
+ yields a near-empty database that still opens cleanly.
82
+
83
+ ## Development
84
+
85
+ ```sh
86
+ conda env create -f environment.yml # or: conda create -n outrage -c conda-forge python=3.14
87
+ conda activate outrage
88
+ pip install -e ".[dev]"
89
+ pytest
90
+ ruff check . && ruff format --check .
91
+ ```
92
+
93
+ The stores live in a directory given to the server by `--dir` or `RAGE_DIR`,
94
+ defaulting to `./.outrage/` in the working directory, each as a file inside it:
95
+ `--root-mount FILE` names the one answering for everything (default
96
+ `store.sqlite`) and `--mount KEY=FILE` mounts another under a key. A store file
97
+ is always relative to the directory, so only `--dir` is a path. See
98
+ [design.md](design.md#store-location).
99
+
100
+ `outrage-server --log` records requests and store accesses as JSON lines, by
101
+ default in `log.jsonl` beside the store. It is off otherwise, since it records
102
+ document text. `--log-content none|excerpt|full` controls how much of that text
103
+ it keeps.
104
+
105
+ `.mcp.json` registers the server for this project. Its `command` is an absolute
106
+ path into the conda environment, so it is specific to the machine it was written
107
+ on; adjust it after creating the environment elsewhere.