keb-consol 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.
@@ -0,0 +1,26 @@
1
+ # build output
2
+ /target/
3
+ node_modules/
4
+ dist/
5
+ *.tgz
6
+ __pycache__/
7
+ *.pyc
8
+ .venv/
9
+ .ruff_cache/
10
+
11
+ # secrets and local state: never commit
12
+ .env
13
+ .env.*
14
+ *.sqlite
15
+ *.sqlite-shm
16
+ *.sqlite-wal
17
+ .consol.lock
18
+ /vault/
19
+ /models/
20
+
21
+ # examples: own cargo target, default vault root
22
+ /examples/axum/target/
23
+ /examples/*/vaults/
24
+ /examples/axum/Cargo.lock
25
+ /examples/*/bun.lock
26
+ /examples/*/uv.lock
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kryat, Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,203 @@
1
+ Metadata-Version: 2.5
2
+ Name: keb-consol
3
+ Version: 0.1.0
4
+ Summary: Python SDK for the consol memory engine: a local vault, a hosted client, and the backend API
5
+ Author: Khas
6
+ License-Expression: MIT
7
+ License-File: LICENSE
8
+ Requires-Python: >=3.10
9
+ Description-Content-Type: text/markdown
10
+
11
+ # keb-consol
12
+
13
+ Python SDK for the consol memory engine. Distribution `keb-consol`, import name `consol`; Python
14
+ >= 3.10, no dependencies, synchronous and thread-safe, typed payloads (`TypedDict`s, `py.typed`).
15
+ Three uses, one behaviour: a vault in your process, a client of a hosted backend, or your own
16
+ backend. Not published yet; install from a checkout with `uv pip install ./bindings/py`.
17
+
18
+ ## Prerequisites
19
+
20
+ - **The `consol` binary.** The wheel does not bundle it. The SDK uses the first of: the
21
+ `binary_path` argument, the `CONSOL_BIN` environment variable, `consol` (`consol.exe`) on `PATH`.
22
+ An explicit path that points nowhere fails at once with `binary_not_found`.
23
+ - **A database** for local and backend use: PostgreSQL with pgvector, or CockroachDB. Pass
24
+ `db='postgres://...'` or set `CONSOL_DB_URL` (the one `CONSOL_*` variable the child inherits).
25
+
26
+ ## 1. Local: a vault in your process
27
+
28
+ ```python
29
+ from consol import create_vault
30
+
31
+ with create_vault('./memory', agent='assistant') as vault:
32
+ vault.remember(id='deploy-day', content='Deploys run on Fridays.')
33
+ hits = vault.recall(query='deploys run')
34
+ assert 'deploy-day' in [card['source']['id'] for card in hits['candidates']]
35
+ print(vault.read(id='deploy-day')['text']) # the complete Markdown file, frontmatter included
36
+ ```
37
+
38
+ `create_vault` starts `consol stdio --vault ./memory` and creates the vault (`vault_exists` if there
39
+ is one); `open_vault('./memory', agent=...)` opens an existing one (`vault_not_found`). Both check
40
+ the server version against the SDK's before they return. For an LLM tool loop, `call_tool(name,
41
+ args)` returns the whole reply and never raises for a tool error: send `reply['text']` back to the
42
+ model. `consol schema` prints the JSON Schema of the wire.
43
+
44
+ ## 2. Hosted: a client of a backend
45
+
46
+ ```py
47
+ from consol import open_vault
48
+
49
+ with open_vault(url='https://memory.example.com/v1/consol',
50
+ headers={'Authorization': 'Bearer <your key>'}, # whatever your backend checks
51
+ vault='acme', agent='alice') as vault:
52
+ vault.remember(id='deploy-day', content='Deploys run on Fridays.')
53
+ print(vault.recall(query='deploys run')['candidates'][0]['source']['id'])
54
+ ```
55
+
56
+ The same methods over HTTP: each call POSTs `{"tool", "vault", "agent", "args"}` with your headers.
57
+ There is no create: backends create vaults. `vault` and `agent` may be left out when the backend
58
+ pins them. Redirects are never followed (they would carry your credentials). Transport failures are
59
+ `request_failed` (`status`; retryable for connect errors and 502/503/504) and `forbidden` for a 401
60
+ or 403 that is not a consol reply; a consol error body is a normal tool error whatever its status.
61
+
62
+ ## 3. Backend: your own endpoint
63
+
64
+ ```py
65
+ import asyncio
66
+ from consol import Consol, Context, http_status
67
+ from fastapi import FastAPI, Header, HTTPException, Request
68
+ from fastapi.responses import JSONResponse
69
+
70
+ consol = Consol.start(vault_root='./vaults') # or vault='./one-vault'; db= or CONSOL_DB_URL
71
+ app = FastAPI()
72
+ USERS = {'alice-token': ('acme', 'alice')} # YOUR auth: token -> (vault, agent)
73
+
74
+ @app.post('/v1/consol')
75
+ async def wire(request: Request, authorization: str = Header('')):
76
+ user = USERS.get(authorization.removeprefix('Bearer '))
77
+ if user is None:
78
+ raise HTTPException(401, 'unauthorized')
79
+ ctx = Context(vaults=[user[0]], agent=user[1]) # from your auth, NEVER from the request
80
+ reply = await asyncio.to_thread(consol.handle, await request.json(), ctx) # handle is sync
81
+ return JSONResponse(reply, status_code=http_status(reply))
82
+ ```
83
+
84
+ `ctx` decides which vaults and which agent a request may use: build it from your own authentication,
85
+ never from the body or headers a client controls (`{'vaults': None, ...}` means every vault; write it
86
+ deliberately). `handle(body, ctx)` returns the reply dict as is (`version`, `text`, `result` or
87
+ `error`); a tool error is in the reply, never raised, and `http_status(reply)` is the status to
88
+ answer with. One child serves every thread. Creating a vault is the backend's job, never the wire's:
89
+ `consol.create_vault('acme')` (`vault_exists`), `consol.open_vault('acme')` (`vault_not_found`).
90
+ `Consol.start` takes `vault_root` (a directory or `s3://bucket/prefix`; vault `x` is `<root>/x`) or
91
+ `vault` (one vault, named `default`), plus `db`, `embedding`, `jev={'url', 'api_key', 'model',
92
+ 'only', 'auto'}`, `binary_path`, `env` and `timeout`.
93
+
94
+ ## Methods
95
+
96
+ Keyword-only arguments with the tool's wire names, plus `timeout=` in seconds; they return the
97
+ result payload as a plain dict and raise `ConsolError` on a tool error.
98
+
99
+ | Method | Arguments | Returns |
100
+ |---|---|---|
101
+ | `recall` | `query`, `include_history`, `limit`, `agent` | `RecallResult`: `text` is the progressive disclosure to give a model (cards by tier, intent block, hints); `candidates` are the same cards as data. Cards are leads: read a note before relying on it |
102
+ | `read` | `id`, `continuation`, `agent` | `ReadResult`; `text` is the exact file |
103
+ | `remember` | `id`, `content`, `expected_version`, `summary`, `entities`, `facets`, `scope`, `team`, `valid_from`, `valid_to`, `reflection`, `agent` | `RememberResult` |
104
+ | `remove` | `id`, `expected_version`, `reflection`, `agent` | `RemoveResult`: the note is archived, its body stays readable |
105
+ | `reflect` | one mode: none (discovery), `ids`, `work_ids` or `decisions`; `limit`, `agent` | `ReflectResult`; delivered sources carry their `text` |
106
+ | `call_tool` | `(name, args)` with raw wire arguments | the whole `Reply`; a tool error is a reply with `error` |
107
+ | `close` | | final and idempotent |
108
+
109
+ - A call's `agent` goes into the envelope and overrides the vault's default.
110
+ - `remember` without `expected_version` is create-only. To update, pass `source.content_version`
111
+ from your last `read`; a stale one fails with `version_conflict`. `None` keeps a stored value,
112
+ `''` or `[]` clears it. `remember` and `remove` also take `resume=True` with `reflection`
113
+ (`{'work_id', 'basis_hash', 'action_key'}`) to resume a pending reflection-bound write.
114
+ - `byte_range` values are UTF-8 byte offsets: `slice_utf8(text, byte_range)` slices a `str` by them.
115
+
116
+ ## Loading many notes
117
+
118
+ Each `remember` is independent, and the vault is safe to call from several threads, so load a
119
+ corpus in parallel: with a hosted embedding provider most of a write is waiting on it, and
120
+ parallel writes overlap that wait (8 writers loaded 3.8x faster than one in a measured run).
121
+
122
+ ```py
123
+ from concurrent.futures import ThreadPoolExecutor
124
+
125
+ with ThreadPoolExecutor(8) as pool:
126
+ list(pool.map(lambda doc: vault.remember(**doc), docs)) # docs: [{"id": ..., "content": ...}]
127
+ ```
128
+
129
+ ## Errors
130
+
131
+ Every failure is a `ConsolError`. Branch on `code` and `retryable`, never on the message:
132
+
133
+ ```python
134
+ from consol import ConsolError, open_vault
135
+
136
+ with open_vault('./memory', agent='assistant') as vault:
137
+ try:
138
+ vault.remember(id='deploy-day', content='Deploys run on Mondays.') # create-only: the id exists
139
+ except ConsolError as error:
140
+ if error.code != 'already_exists':
141
+ raise
142
+ print(error.code, error.retryable, error.tool) # already_exists False remember
143
+ ```
144
+
145
+ - `field` names the input at fault, relative to `args` (`limit`, `decisions[2].temporal`); `tool`
146
+ the tool; `result` keeps the raw reply of a failed call, including the durable state of a pending
147
+ write (`error.result['result']`).
148
+ - Engine codes (accept unknown future ones): `invalid_argument`, `forbidden`, `not_found`,
149
+ `already_exists`, `version_conflict`, `vault_exists`, `vault_not_found`, `team_not_attached`,
150
+ `secret_rejected`, `content_too_large`, `unsupported`, `work_not_found`, `basis_mismatch`,
151
+ `already_decided`, `unresolved_writes`, `postcondition_failed`, `index_unavailable`, `storage_unavailable`, `vault_busy`,
152
+ `internal`.
153
+ - SDK codes: `request_failed` (hosted), `incompatible_server` (server major.minor differs from the
154
+ SDK's), `binary_not_found`, `server_exited`, `timeout`, `closed`.
155
+ - `retryable: True` (`vault_busy`, `index_unavailable`, `storage_unavailable`, connect errors) means the identical request
156
+ may succeed later. The SDK never retries by itself.
157
+
158
+ ## Lifecycle
159
+
160
+ - Use the vault (or `Consol`) as a context manager or call `close()`: final and idempotent.
161
+ In-flight and later calls fail with `closed`; a concurrent `close()` returns once the child exited.
162
+ - Calls may overlap; replies are matched by id. Every call has a deadline: 300 s by default,
163
+ `timeout=` per vault or per call, `0` or `None` disables it. A timeout only stops waiting: a write
164
+ may still complete, so read the note before you retry.
165
+ - When the child crashes, in-flight calls fail with `server_exited`. The next call starts it once
166
+ more (a local vault is re-opened first: deleted meanwhile means `vault_not_found`).
167
+ - Run one child per vault; a second process waits up to 5 s for the vault lock, then `vault_busy`.
168
+
169
+ ## Configuration and logging
170
+
171
+ Arguments become the child's environment, never its argv. Inherited `CONSOL_*` and `RUST_LOG` are
172
+ dropped first (except `CONSOL_DB_URL`), so a stray host variable cannot change the agent or the vault.
173
+
174
+ | Argument | Meaning |
175
+ |---|---|
176
+ | `path` / `vault_root` / `vault` | the vault location (made absolute) or, for `Consol`, the vaults' root |
177
+ | `agent` | default agent of every call; a call's `agent` overrides it |
178
+ | `db` | database URL (`CONSOL_DB_URL`, required unless the environment has it) |
179
+ | `embedding` | `{'url', 'model', 'api_key', 'dims', 'query_prefix', 'batch_size', 'timeout_ms'}` (`CONSOL_EMBED_*`; another key fails with `invalid_argument`); without it recall is lexical-only |
180
+ | `env` | any other child variable, applied last |
181
+ | `binary_path` | the binary, see above |
182
+ | `timeout` | default per-call deadline in seconds |
183
+
184
+ The child's stderr is captured and never written to your stderr. Each line goes to
185
+ `logging.getLogger('consol')` at DEBUG level; add `env={'RUST_LOG': 'debug'}` for more detail. A
186
+ `server_exited` error carries the last lines in its message and in `stderr_tail`.
187
+
188
+ ## Types and stability
189
+
190
+ Exports: `create_vault`, `open_vault`, `Vault`, `Consol`, `http_status`, `ConsolError`,
191
+ `slice_utf8`, `Reply`, `Context` and the result `TypedDict`s (`RecallResult`, `ReadResult`,
192
+ `RememberResult`, `RemoveResult`, `ReflectResult`, `Decision`, `ToolError`, ...). Payloads may gain
193
+ fields in any release. Experimental, so they may change in a minor release: hub and promotion
194
+ work, leads and decisions, and every `jev`, `jev_triage` and `jev_advisory` field (plain dicts).
195
+
196
+ ## Check
197
+
198
+ From the repository root:
199
+
200
+ ```bash
201
+ uv run --no-project python bindings/test_sdk.py # protocol tests: no binary, no database
202
+ CONSOL_BIN=/abs/consol CONSOL_DB_URL=postgres://... uv run --no-project python bindings/test_sdk.py real
203
+ ```
@@ -0,0 +1,193 @@
1
+ # keb-consol
2
+
3
+ Python SDK for the consol memory engine. Distribution `keb-consol`, import name `consol`; Python
4
+ >= 3.10, no dependencies, synchronous and thread-safe, typed payloads (`TypedDict`s, `py.typed`).
5
+ Three uses, one behaviour: a vault in your process, a client of a hosted backend, or your own
6
+ backend. Not published yet; install from a checkout with `uv pip install ./bindings/py`.
7
+
8
+ ## Prerequisites
9
+
10
+ - **The `consol` binary.** The wheel does not bundle it. The SDK uses the first of: the
11
+ `binary_path` argument, the `CONSOL_BIN` environment variable, `consol` (`consol.exe`) on `PATH`.
12
+ An explicit path that points nowhere fails at once with `binary_not_found`.
13
+ - **A database** for local and backend use: PostgreSQL with pgvector, or CockroachDB. Pass
14
+ `db='postgres://...'` or set `CONSOL_DB_URL` (the one `CONSOL_*` variable the child inherits).
15
+
16
+ ## 1. Local: a vault in your process
17
+
18
+ ```python
19
+ from consol import create_vault
20
+
21
+ with create_vault('./memory', agent='assistant') as vault:
22
+ vault.remember(id='deploy-day', content='Deploys run on Fridays.')
23
+ hits = vault.recall(query='deploys run')
24
+ assert 'deploy-day' in [card['source']['id'] for card in hits['candidates']]
25
+ print(vault.read(id='deploy-day')['text']) # the complete Markdown file, frontmatter included
26
+ ```
27
+
28
+ `create_vault` starts `consol stdio --vault ./memory` and creates the vault (`vault_exists` if there
29
+ is one); `open_vault('./memory', agent=...)` opens an existing one (`vault_not_found`). Both check
30
+ the server version against the SDK's before they return. For an LLM tool loop, `call_tool(name,
31
+ args)` returns the whole reply and never raises for a tool error: send `reply['text']` back to the
32
+ model. `consol schema` prints the JSON Schema of the wire.
33
+
34
+ ## 2. Hosted: a client of a backend
35
+
36
+ ```py
37
+ from consol import open_vault
38
+
39
+ with open_vault(url='https://memory.example.com/v1/consol',
40
+ headers={'Authorization': 'Bearer <your key>'}, # whatever your backend checks
41
+ vault='acme', agent='alice') as vault:
42
+ vault.remember(id='deploy-day', content='Deploys run on Fridays.')
43
+ print(vault.recall(query='deploys run')['candidates'][0]['source']['id'])
44
+ ```
45
+
46
+ The same methods over HTTP: each call POSTs `{"tool", "vault", "agent", "args"}` with your headers.
47
+ There is no create: backends create vaults. `vault` and `agent` may be left out when the backend
48
+ pins them. Redirects are never followed (they would carry your credentials). Transport failures are
49
+ `request_failed` (`status`; retryable for connect errors and 502/503/504) and `forbidden` for a 401
50
+ or 403 that is not a consol reply; a consol error body is a normal tool error whatever its status.
51
+
52
+ ## 3. Backend: your own endpoint
53
+
54
+ ```py
55
+ import asyncio
56
+ from consol import Consol, Context, http_status
57
+ from fastapi import FastAPI, Header, HTTPException, Request
58
+ from fastapi.responses import JSONResponse
59
+
60
+ consol = Consol.start(vault_root='./vaults') # or vault='./one-vault'; db= or CONSOL_DB_URL
61
+ app = FastAPI()
62
+ USERS = {'alice-token': ('acme', 'alice')} # YOUR auth: token -> (vault, agent)
63
+
64
+ @app.post('/v1/consol')
65
+ async def wire(request: Request, authorization: str = Header('')):
66
+ user = USERS.get(authorization.removeprefix('Bearer '))
67
+ if user is None:
68
+ raise HTTPException(401, 'unauthorized')
69
+ ctx = Context(vaults=[user[0]], agent=user[1]) # from your auth, NEVER from the request
70
+ reply = await asyncio.to_thread(consol.handle, await request.json(), ctx) # handle is sync
71
+ return JSONResponse(reply, status_code=http_status(reply))
72
+ ```
73
+
74
+ `ctx` decides which vaults and which agent a request may use: build it from your own authentication,
75
+ never from the body or headers a client controls (`{'vaults': None, ...}` means every vault; write it
76
+ deliberately). `handle(body, ctx)` returns the reply dict as is (`version`, `text`, `result` or
77
+ `error`); a tool error is in the reply, never raised, and `http_status(reply)` is the status to
78
+ answer with. One child serves every thread. Creating a vault is the backend's job, never the wire's:
79
+ `consol.create_vault('acme')` (`vault_exists`), `consol.open_vault('acme')` (`vault_not_found`).
80
+ `Consol.start` takes `vault_root` (a directory or `s3://bucket/prefix`; vault `x` is `<root>/x`) or
81
+ `vault` (one vault, named `default`), plus `db`, `embedding`, `jev={'url', 'api_key', 'model',
82
+ 'only', 'auto'}`, `binary_path`, `env` and `timeout`.
83
+
84
+ ## Methods
85
+
86
+ Keyword-only arguments with the tool's wire names, plus `timeout=` in seconds; they return the
87
+ result payload as a plain dict and raise `ConsolError` on a tool error.
88
+
89
+ | Method | Arguments | Returns |
90
+ |---|---|---|
91
+ | `recall` | `query`, `include_history`, `limit`, `agent` | `RecallResult`: `text` is the progressive disclosure to give a model (cards by tier, intent block, hints); `candidates` are the same cards as data. Cards are leads: read a note before relying on it |
92
+ | `read` | `id`, `continuation`, `agent` | `ReadResult`; `text` is the exact file |
93
+ | `remember` | `id`, `content`, `expected_version`, `summary`, `entities`, `facets`, `scope`, `team`, `valid_from`, `valid_to`, `reflection`, `agent` | `RememberResult` |
94
+ | `remove` | `id`, `expected_version`, `reflection`, `agent` | `RemoveResult`: the note is archived, its body stays readable |
95
+ | `reflect` | one mode: none (discovery), `ids`, `work_ids` or `decisions`; `limit`, `agent` | `ReflectResult`; delivered sources carry their `text` |
96
+ | `call_tool` | `(name, args)` with raw wire arguments | the whole `Reply`; a tool error is a reply with `error` |
97
+ | `close` | | final and idempotent |
98
+
99
+ - A call's `agent` goes into the envelope and overrides the vault's default.
100
+ - `remember` without `expected_version` is create-only. To update, pass `source.content_version`
101
+ from your last `read`; a stale one fails with `version_conflict`. `None` keeps a stored value,
102
+ `''` or `[]` clears it. `remember` and `remove` also take `resume=True` with `reflection`
103
+ (`{'work_id', 'basis_hash', 'action_key'}`) to resume a pending reflection-bound write.
104
+ - `byte_range` values are UTF-8 byte offsets: `slice_utf8(text, byte_range)` slices a `str` by them.
105
+
106
+ ## Loading many notes
107
+
108
+ Each `remember` is independent, and the vault is safe to call from several threads, so load a
109
+ corpus in parallel: with a hosted embedding provider most of a write is waiting on it, and
110
+ parallel writes overlap that wait (8 writers loaded 3.8x faster than one in a measured run).
111
+
112
+ ```py
113
+ from concurrent.futures import ThreadPoolExecutor
114
+
115
+ with ThreadPoolExecutor(8) as pool:
116
+ list(pool.map(lambda doc: vault.remember(**doc), docs)) # docs: [{"id": ..., "content": ...}]
117
+ ```
118
+
119
+ ## Errors
120
+
121
+ Every failure is a `ConsolError`. Branch on `code` and `retryable`, never on the message:
122
+
123
+ ```python
124
+ from consol import ConsolError, open_vault
125
+
126
+ with open_vault('./memory', agent='assistant') as vault:
127
+ try:
128
+ vault.remember(id='deploy-day', content='Deploys run on Mondays.') # create-only: the id exists
129
+ except ConsolError as error:
130
+ if error.code != 'already_exists':
131
+ raise
132
+ print(error.code, error.retryable, error.tool) # already_exists False remember
133
+ ```
134
+
135
+ - `field` names the input at fault, relative to `args` (`limit`, `decisions[2].temporal`); `tool`
136
+ the tool; `result` keeps the raw reply of a failed call, including the durable state of a pending
137
+ write (`error.result['result']`).
138
+ - Engine codes (accept unknown future ones): `invalid_argument`, `forbidden`, `not_found`,
139
+ `already_exists`, `version_conflict`, `vault_exists`, `vault_not_found`, `team_not_attached`,
140
+ `secret_rejected`, `content_too_large`, `unsupported`, `work_not_found`, `basis_mismatch`,
141
+ `already_decided`, `unresolved_writes`, `postcondition_failed`, `index_unavailable`, `storage_unavailable`, `vault_busy`,
142
+ `internal`.
143
+ - SDK codes: `request_failed` (hosted), `incompatible_server` (server major.minor differs from the
144
+ SDK's), `binary_not_found`, `server_exited`, `timeout`, `closed`.
145
+ - `retryable: True` (`vault_busy`, `index_unavailable`, `storage_unavailable`, connect errors) means the identical request
146
+ may succeed later. The SDK never retries by itself.
147
+
148
+ ## Lifecycle
149
+
150
+ - Use the vault (or `Consol`) as a context manager or call `close()`: final and idempotent.
151
+ In-flight and later calls fail with `closed`; a concurrent `close()` returns once the child exited.
152
+ - Calls may overlap; replies are matched by id. Every call has a deadline: 300 s by default,
153
+ `timeout=` per vault or per call, `0` or `None` disables it. A timeout only stops waiting: a write
154
+ may still complete, so read the note before you retry.
155
+ - When the child crashes, in-flight calls fail with `server_exited`. The next call starts it once
156
+ more (a local vault is re-opened first: deleted meanwhile means `vault_not_found`).
157
+ - Run one child per vault; a second process waits up to 5 s for the vault lock, then `vault_busy`.
158
+
159
+ ## Configuration and logging
160
+
161
+ Arguments become the child's environment, never its argv. Inherited `CONSOL_*` and `RUST_LOG` are
162
+ dropped first (except `CONSOL_DB_URL`), so a stray host variable cannot change the agent or the vault.
163
+
164
+ | Argument | Meaning |
165
+ |---|---|
166
+ | `path` / `vault_root` / `vault` | the vault location (made absolute) or, for `Consol`, the vaults' root |
167
+ | `agent` | default agent of every call; a call's `agent` overrides it |
168
+ | `db` | database URL (`CONSOL_DB_URL`, required unless the environment has it) |
169
+ | `embedding` | `{'url', 'model', 'api_key', 'dims', 'query_prefix', 'batch_size', 'timeout_ms'}` (`CONSOL_EMBED_*`; another key fails with `invalid_argument`); without it recall is lexical-only |
170
+ | `env` | any other child variable, applied last |
171
+ | `binary_path` | the binary, see above |
172
+ | `timeout` | default per-call deadline in seconds |
173
+
174
+ The child's stderr is captured and never written to your stderr. Each line goes to
175
+ `logging.getLogger('consol')` at DEBUG level; add `env={'RUST_LOG': 'debug'}` for more detail. A
176
+ `server_exited` error carries the last lines in its message and in `stderr_tail`.
177
+
178
+ ## Types and stability
179
+
180
+ Exports: `create_vault`, `open_vault`, `Vault`, `Consol`, `http_status`, `ConsolError`,
181
+ `slice_utf8`, `Reply`, `Context` and the result `TypedDict`s (`RecallResult`, `ReadResult`,
182
+ `RememberResult`, `RemoveResult`, `ReflectResult`, `Decision`, `ToolError`, ...). Payloads may gain
183
+ fields in any release. Experimental, so they may change in a minor release: hub and promotion
184
+ work, leads and decisions, and every `jev`, `jev_triage` and `jev_advisory` field (plain dicts).
185
+
186
+ ## Check
187
+
188
+ From the repository root:
189
+
190
+ ```bash
191
+ uv run --no-project python bindings/test_sdk.py # protocol tests: no binary, no database
192
+ CONSOL_BIN=/abs/consol CONSOL_DB_URL=postgres://... uv run --no-project python bindings/test_sdk.py real
193
+ ```
@@ -0,0 +1,15 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "keb-consol"
7
+ version = "0.1.0"
8
+ description = "Python SDK for the consol memory engine: a local vault, a hosted client, and the backend API"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "MIT"
12
+ authors = [{ name = "Khas" }]
13
+
14
+ [tool.hatch.build.targets.wheel]
15
+ packages = ["src/consol"]
@@ -0,0 +1,18 @@
1
+ from consol.client import (Consol, ConsolError, Vault, __version__, create_vault, http_status,
2
+ open_vault, slice_utf8)
3
+ from consol.types import (AffectedDoc, Context, Decision, DecisionReceipt, DeliveredSource,
4
+ EmbeddingConfig, EntityAffectedGroup, ReadResult,
5
+ RecallCard, RecallResult,
6
+ ReflectCounters, ReflectDelivery, ReflectDiscovery,
7
+ ReflectionContext, ReflectionWork,
8
+ ReflectResult, Relationship, RememberResult, RemoveResult,
9
+ Reply, SourceRef, ToolError,
10
+ WorkBasis, WriteOutcome)
11
+
12
+ __all__ = ["AffectedDoc", "Consol", "ConsolError", "Context", "Decision", "DecisionReceipt",
13
+ "DeliveredSource", "EmbeddingConfig", "EntityAffectedGroup", "ReadResult",
14
+ "RecallCard", "RecallResult", "ReflectCounters", "ReflectDelivery",
15
+ "ReflectDiscovery", "ReflectionContext", "ReflectionWork",
16
+ "ReflectResult", "Relationship", "RememberResult", "RemoveResult", "Reply",
17
+ "SourceRef", "ToolError", "Vault", "WorkBasis", "WriteOutcome", "__version__",
18
+ "create_vault", "http_status", "open_vault", "slice_utf8"]