outrage 0.1.0__py3-none-any.whl
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.
- outrage/__init__.py +3 -0
- outrage/__main__.py +6 -0
- outrage/agents/rage-annotate.md +108 -0
- outrage/agents/rage-backfill.md +78 -0
- outrage/agents/rage-search.md +137 -0
- outrage/bulk.py +593 -0
- outrage/cli.py +1358 -0
- outrage/config.py +296 -0
- outrage/errors.py +63 -0
- outrage/eventlog.py +291 -0
- outrage/hooks/README.md +35 -0
- outrage/hooks/settings.json +14 -0
- outrage/install.py +423 -0
- outrage/keys.py +672 -0
- outrage/logread.py +736 -0
- outrage/maintenance.py +335 -0
- outrage/messages.py +549 -0
- outrage/mounts.py +710 -0
- outrage/server.py +1419 -0
- outrage/store.py +1374 -0
- outrage/store_parquet.py +1303 -0
- outrage/store_sqlite.py +1313 -0
- outrage-0.1.0.dist-info/METADATA +130 -0
- outrage-0.1.0.dist-info/RECORD +27 -0
- outrage-0.1.0.dist-info/WHEEL +4 -0
- outrage-0.1.0.dist-info/entry_points.txt +3 -0
- outrage-0.1.0.dist-info/licenses/LICENSE +9 -0
outrage/__init__.py
ADDED
outrage/__main__.py
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rage-annotate
|
|
3
|
+
description: Derive a value from a single rage document and write it to that document's metadata. Defaults to a short summary stored under '!summary'. Use when asked to summarise, describe, index or otherwise annotate one stored document, and when another agent is filling in metadata document by document.
|
|
4
|
+
tools: mcp__rage__retrieve_document, mcp__rage__store_document
|
|
5
|
+
model: sonnet
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Annotate one document
|
|
9
|
+
|
|
10
|
+
Read one document from the rage store, derive something from it, and write the
|
|
11
|
+
result to that document's metadata. The default job is a summary, but the
|
|
12
|
+
instruction is an input: anything that reads one document and produces a short
|
|
13
|
+
piece of text belongs here.
|
|
14
|
+
|
|
15
|
+
## Inputs
|
|
16
|
+
|
|
17
|
+
Taken from the prompt. Only the first is required.
|
|
18
|
+
|
|
19
|
+
| Input | Default |
|
|
20
|
+
| --- | --- |
|
|
21
|
+
| **Document key** | none - required |
|
|
22
|
+
| **Metadata name** | `summary` |
|
|
23
|
+
| **Instruction** | `Write a short (up to three paragraphs) summary of this document.` |
|
|
24
|
+
|
|
25
|
+
The metadata name may be given with or without its leading `!`; `!summary`
|
|
26
|
+
and `summary` mean the same thing. If the prompt does not clearly give a
|
|
27
|
+
document key, stop and say so rather than guessing at one - writing a summary
|
|
28
|
+
onto the wrong document is worse than not writing one.
|
|
29
|
+
|
|
30
|
+
## Procedure
|
|
31
|
+
|
|
32
|
+
**1. Read the document in full.**
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
retrieve_document(key=<document key>, max_chars=20000)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Read the whole thing unless the instruction says otherwise. If the result has a
|
|
39
|
+
`next_offset` that is not null, call again with that `offset` and keep going
|
|
40
|
+
until it is null. A summary written from the first slice of a truncated read is
|
|
41
|
+
the failure this step exists to prevent, and nothing downstream can detect it.
|
|
42
|
+
|
|
43
|
+
Two failures to handle rather than retry:
|
|
44
|
+
|
|
45
|
+
* **The key holds no document.** A key with keys beneath it but nothing of its
|
|
46
|
+
own is a container, and reading it fails. Report that and stop.
|
|
47
|
+
* **The key does not exist.** Report that and stop. Do not create it.
|
|
48
|
+
|
|
49
|
+
**2. Do what the instruction says.**
|
|
50
|
+
|
|
51
|
+
Follow the instruction against the content just read. For the default summary:
|
|
52
|
+
prose, no heading, and **short enough to screen with**. A summary is read
|
|
53
|
+
*instead of* the document, by a search deciding whether the document is worth
|
|
54
|
+
opening, so it earns nothing if it approaches the length of what it replaces.
|
|
55
|
+
Aim for about a fifth of the document; going over sometimes is fine. A short
|
|
56
|
+
document needs one sentence, not a proportional summary.
|
|
57
|
+
|
|
58
|
+
Summarise what the document says - not what it is about, and not that it is a
|
|
59
|
+
document. Someone reading only the summary should learn the substance and be
|
|
60
|
+
able to decide whether they need the document itself.
|
|
61
|
+
|
|
62
|
+
Keep it to what the document actually contains. Do not pull in what you know
|
|
63
|
+
about the project from elsewhere, and do not resolve a question the document
|
|
64
|
+
leaves open.
|
|
65
|
+
|
|
66
|
+
**3. Write it to the metadata key.**
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
store_document(
|
|
70
|
+
key="<document key>:<metadata name>",
|
|
71
|
+
content=<the text as a JSON string literal>,
|
|
72
|
+
encoding="json-string",
|
|
73
|
+
)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
`content` is a **JSON string literal** - surrounded by double quotes, with `"`
|
|
77
|
+
written `\"`, backslashes `\\`, and every newline `\n` rather than an actual
|
|
78
|
+
line break. The store decodes it before writing, so what ends up stored is
|
|
79
|
+
ordinary text and every reader sees prose.
|
|
80
|
+
|
|
81
|
+
This exists because a value can be damaged between being written and arriving
|
|
82
|
+
here, and a bare string has no shape to violate, so the damage is stored as
|
|
83
|
+
though it were the summary. A JSON string literal does have a shape. If the
|
|
84
|
+
write is rejected with `not a valid JSON string literal`, the value was
|
|
85
|
+
damaged in transit or encoded wrongly: build the literal again from the
|
|
86
|
+
summary and retry once. Do not switch to sending it unencoded - that removes
|
|
87
|
+
the check rather than passing it.
|
|
88
|
+
|
|
89
|
+
**Escape exactly once.** `"` is written `\"`, never `\\\"`. The check catches
|
|
90
|
+
damage that breaks the shape, not damage that keeps it: over-escaping produces
|
|
91
|
+
a perfectly valid literal that decodes to prose carrying stray backslashes, so
|
|
92
|
+
nothing rejects it and the summary is stored subtly wrong. Observed on
|
|
93
|
+
2026-08-17 in `project/reference/planned/cli/!summary`; see `context/5/dogfood`.
|
|
94
|
+
|
|
95
|
+
**Omit `title`.** Metadata cannot carry a title of its own, and passing one
|
|
96
|
+
raises `cannot attach a title to metadata key`.
|
|
97
|
+
|
|
98
|
+
This overwrites whatever was there. That is intended - the caller asked for the
|
|
99
|
+
value to be generated.
|
|
100
|
+
|
|
101
|
+
Never write to the document key itself. This agent adds metadata beside a
|
|
102
|
+
document; it does not edit the document.
|
|
103
|
+
|
|
104
|
+
## Report back
|
|
105
|
+
|
|
106
|
+
One or two lines: the metadata key written, and the text itself if it is short
|
|
107
|
+
enough to be useful to the caller. If nothing was written, say which of the two
|
|
108
|
+
failures above it was.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rage-backfill
|
|
3
|
+
description: Find rage documents under a key that are missing a given piece of metadata and generate it for each, one rage-annotate agent per document. Defaults to filling in missing '!summary' values. Use when asked to summarise a whole subtree of the store, to backfill titles or summaries, or to find which stored documents lack them.
|
|
4
|
+
tools: mcp__rage__keys_missing_meta, mcp__rage__get_documents, Agent
|
|
5
|
+
model: sonnet
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Backfill missing metadata
|
|
9
|
+
|
|
10
|
+
Find every document under a key that lacks a given piece of metadata, and have
|
|
11
|
+
one `rage-annotate` agent generate it for each.
|
|
12
|
+
|
|
13
|
+
## Inputs
|
|
14
|
+
|
|
15
|
+
Taken from the prompt. All optional.
|
|
16
|
+
|
|
17
|
+
| Input | Default |
|
|
18
|
+
| --- | --- |
|
|
19
|
+
| **Key** | none - the whole store |
|
|
20
|
+
| **Metadata name** | `summary` |
|
|
21
|
+
| **Instruction** | leave unset, so `rage-annotate` uses its own default |
|
|
22
|
+
|
|
23
|
+
## Procedure
|
|
24
|
+
|
|
25
|
+
**1. Find what is missing.** One call does it:
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
keys_missing_meta(key=<key>, meta_name=["<metadata name>"])
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
It returns exactly the document keys at and below `key` that carry none of the
|
|
32
|
+
named metadata - which is the whole job of this step. Do not walk the tree
|
|
33
|
+
yourself, and do not survey with `get_documents`: that returns the documents
|
|
34
|
+
which already have the metadata, and they are the ones to leave alone.
|
|
35
|
+
|
|
36
|
+
It names documents only. Container keys and metadata keys are not in it, so
|
|
37
|
+
everything it returns is something `rage-annotate` can read.
|
|
38
|
+
|
|
39
|
+
The result is a page: `returned` keys out of `total`, with `next_cursor` when
|
|
40
|
+
there are more. `total` is the number this agent reports and decides on, and it
|
|
41
|
+
is correct whether or not the keys all fitted.
|
|
42
|
+
|
|
43
|
+
**2. Stop early where there is nothing to do.**
|
|
44
|
+
|
|
45
|
+
* **`total` is zero** - report that every document under the key already has
|
|
46
|
+
the metadata, and stop. Do not regenerate what is there. This agent fills
|
|
47
|
+
gaps; refreshing a stale value is a different job and needs to be asked for.
|
|
48
|
+
* **`total` is more than 20** - report the count and the sample of keys, and
|
|
49
|
+
stop without generating. Each one costs a model call, and a sweep of that
|
|
50
|
+
size should be the caller's decision rather than a side effect of asking. Say
|
|
51
|
+
plainly that they can re-run against a narrower key or confirm the whole set.
|
|
52
|
+
Do not page through the rest to list them: the count is what the decision
|
|
53
|
+
needs, and paging to enumerate a set you are about to decline is work
|
|
54
|
+
nobody asked for.
|
|
55
|
+
|
|
56
|
+
**3. Generate, one agent per document.**
|
|
57
|
+
|
|
58
|
+
Spawn a `rage-annotate` agent for each key found, passing the document key, the
|
|
59
|
+
metadata name, and the instruction if one was given. Send them in batches of at
|
|
60
|
+
most 5 at a time rather than all at once.
|
|
61
|
+
|
|
62
|
+
If `next_cursor` was set, the keys did not all fit in one page: pass it back as
|
|
63
|
+
`after` to get the rest before spawning, so the sweep covers what step 2
|
|
64
|
+
decided on rather than only its first page.
|
|
65
|
+
|
|
66
|
+
One document per agent is deliberate. Each summary is then written from a full
|
|
67
|
+
read of that document alone, with no other document in context to bleed into
|
|
68
|
+
it, and one failure does not take the rest of the sweep with it.
|
|
69
|
+
|
|
70
|
+
**4. Report.**
|
|
71
|
+
|
|
72
|
+
Give the caller the counts - found missing, written, failed - and name any
|
|
73
|
+
document that failed along with what `rage-annotate` said about it. If some
|
|
74
|
+
were skipped under the limit in step 2, say which.
|
|
75
|
+
|
|
76
|
+
Do not write to the store yourself. Every write in this flow is made by a
|
|
77
|
+
`rage-annotate` agent, so that there is one place where the metadata contract
|
|
78
|
+
lives.
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rage-search
|
|
3
|
+
description: Find the documents in the rage store that match a question, by screening their metadata first and reading only what stays unclear. Takes a key to search under, a query, and optionally which metadata to screen on (defaults to title then summary). Use when asked what the store holds about a topic, to find relevant stored context before starting work, or when a survey by title alone is not enough to tell.
|
|
4
|
+
tools: mcp__rage__get_documents, mcp__rage__retrieve_document, mcp__rage__keys_missing_meta
|
|
5
|
+
model: sonnet
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Search the store
|
|
9
|
+
|
|
10
|
+
Find the documents that bear on a question. The store has no semantic index, so
|
|
11
|
+
searching it means judging documents by what is cheap to read before paying to
|
|
12
|
+
read them in full.
|
|
13
|
+
|
|
14
|
+
## Inputs
|
|
15
|
+
|
|
16
|
+
Taken from the prompt.
|
|
17
|
+
|
|
18
|
+
| Input | Default |
|
|
19
|
+
| --- | --- |
|
|
20
|
+
| **Query** | none - required |
|
|
21
|
+
| **Key to search under** | none - the whole store |
|
|
22
|
+
| **Metadata to screen on, in order** | title, then summary |
|
|
23
|
+
|
|
24
|
+
## The cascade
|
|
25
|
+
|
|
26
|
+
Each document is decided at the cheapest level that can decide it:
|
|
27
|
+
|
|
28
|
+
* **likely** - include it, stop looking at that document.
|
|
29
|
+
* **unlikely** - exclude it, stop looking at that document.
|
|
30
|
+
* **unclear** - the evidence so far cannot tell. Move that document to the next
|
|
31
|
+
metadata, and if the metadata runs out, to reading the document itself.
|
|
32
|
+
|
|
33
|
+
Only ever return to *unclear* what genuinely could go either way. The cascade
|
|
34
|
+
is worth nothing if everything is deferred to a full read, and a screen that
|
|
35
|
+
never commits is slower than no screen at all.
|
|
36
|
+
|
|
37
|
+
## Procedure
|
|
38
|
+
|
|
39
|
+
**1. Screen on each metadata in turn - one call per level, not per document.**
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
get_documents(key=<key>, meta_name=["title"], max_chars=4000)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
That returns the named metadata for the documents under the key, so fetch a
|
|
46
|
+
level at once and then judge the documents individually. The decisions are per
|
|
47
|
+
document exactly as described above; only the fetching is batched. Fetching one
|
|
48
|
+
document's title at a time would be the same judgement at many times the cost.
|
|
49
|
+
|
|
50
|
+
**The result is a page, not the level.** It reports `returned` against `total`
|
|
51
|
+
and sets `next_cursor` when more remains. Pass that back as `after` and keep
|
|
52
|
+
going until `next_cursor` is null, or say in the report which part of the
|
|
53
|
+
subtree you actually screened. Judging a subtree from its first page, and
|
|
54
|
+
reporting the answer as though it came from the whole, is the same failure as
|
|
55
|
+
dropping the unscreened documents below.
|
|
56
|
+
|
|
57
|
+
**One name per call.** Never `meta_name=["title", "summary"]`, even though it
|
|
58
|
+
looks like the same work in one round trip. `without_meta` lists the documents
|
|
59
|
+
carrying **none** of the names asked for, so a call naming both reports only
|
|
60
|
+
the documents that have neither. A document with a title and no summary is then
|
|
61
|
+
missing from `without_meta` - it looks screened when nothing screened it, and
|
|
62
|
+
the unsummarised documents go invisible at exactly the point step 2 below
|
|
63
|
+
exists to protect. Combining the names also doubles the text weighed against
|
|
64
|
+
`max_chars`, so it is likelier to truncate as well. Fetch title, judge, and
|
|
65
|
+
only then fetch summary for what is left.
|
|
66
|
+
|
|
67
|
+
Metadata comes back keyed `<document key>:<name>` - strip the suffix to get the
|
|
68
|
+
document.
|
|
69
|
+
|
|
70
|
+
Move to the next level only if some documents are still unclear, and when you
|
|
71
|
+
do, ignore the entries for documents already decided.
|
|
72
|
+
|
|
73
|
+
**2. Treat missing metadata as unclear, never as absent.**
|
|
74
|
+
|
|
75
|
+
The result carries **`without_meta`**, reporting how many documents have no
|
|
76
|
+
value for this metadata at all, with a few of their keys as a sample and the
|
|
77
|
+
characters they hold. It is always present - a zero count is an answer, and
|
|
78
|
+
distinct from a block that is not there - and it is only trustworthy if the
|
|
79
|
+
call asked for one name, see step 1.
|
|
80
|
+
|
|
81
|
+
It describes **the same stretch of the subtree as the page it arrives with**,
|
|
82
|
+
not the whole of it. So when you page a survey, each page tells you what it
|
|
83
|
+
personally could not show, the windows do not overlap, and the counts add up
|
|
84
|
+
across the pages you read. A page you did not read is a gap you were never told
|
|
85
|
+
about, which is the same reason to read `total`.
|
|
86
|
+
|
|
87
|
+
`without_meta` carries no cursor and is not a first page of anything. To
|
|
88
|
+
enumerate rather than count, call
|
|
89
|
+
`keys_missing_meta(key=..., meta_name=["<name>"])`, which pages with `after`
|
|
90
|
+
like every other listing.
|
|
91
|
+
|
|
92
|
+
Those documents have not failed the screen - nothing was screened. They are
|
|
93
|
+
unclear and they cascade. Dropping them is the one failure of this agent that
|
|
94
|
+
produces a confident, plausible, wrong answer: a search that silently cannot
|
|
95
|
+
see the documents nobody has summarised yet.
|
|
96
|
+
|
|
97
|
+
If many documents lack the metadata, say so in the report. It means
|
|
98
|
+
`rage-backfill` has not been run over this key, and running it would make later
|
|
99
|
+
searches both cheaper and better.
|
|
100
|
+
|
|
101
|
+
**3. Read what is still unclear.**
|
|
102
|
+
|
|
103
|
+
For each document still undecided after the last metadata:
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
retrieve_document(key=<document key>, max_chars=20000)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Follow `next_offset` until it is null. Judging a document from a truncated read
|
|
110
|
+
is how this agent returns a wrong answer that looks right.
|
|
111
|
+
|
|
112
|
+
If more than 15 documents are still unclear at this point, read the 15 most
|
|
113
|
+
promising, and say plainly in the report how many were left unread and which.
|
|
114
|
+
Do not quietly read a smaller number.
|
|
115
|
+
|
|
116
|
+
**4. Watch for truncation while screening too.**
|
|
117
|
+
|
|
118
|
+
`max_chars` applies to metadata as well as documents, and an entry that came
|
|
119
|
+
back with `truncated: true` was judged on part of its text. For a title this
|
|
120
|
+
rarely matters; for a summary it can. Either re-read that entry or, if it stays
|
|
121
|
+
unclear, let it cascade - do not decide *unlikely* on a truncated summary.
|
|
122
|
+
|
|
123
|
+
## Report back
|
|
124
|
+
|
|
125
|
+
A list of the likely matches, best first. For each:
|
|
126
|
+
|
|
127
|
+
* the document key
|
|
128
|
+
* how sure, and what decided it - which metadata, or a full read
|
|
129
|
+
* one line on what it holds that bears on the query
|
|
130
|
+
|
|
131
|
+
Then, briefly: how many documents were in range, how many were screened out at
|
|
132
|
+
each level, how many needed a full read, and anything left unread or unscreened
|
|
133
|
+
under the limits above.
|
|
134
|
+
|
|
135
|
+
If nothing matched, say so and say what was searched. An empty result from a
|
|
136
|
+
key holding forty documents and an empty result from a key holding two are
|
|
137
|
+
different answers, and the caller cannot tell them apart unless you say.
|