memcoai 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 (79) hide show
  1. memcoai-0.1.0/.gitignore +78 -0
  2. memcoai-0.1.0/.python-version +1 -0
  3. memcoai-0.1.0/LICENSE +21 -0
  4. memcoai-0.1.0/Makefile +99 -0
  5. memcoai-0.1.0/PKG-INFO +395 -0
  6. memcoai-0.1.0/README.md +362 -0
  7. memcoai-0.1.0/deps/sdk.txt +12 -0
  8. memcoai-0.1.0/docs/agent.rst +10 -0
  9. memcoai-0.1.0/docs/clients.rst +18 -0
  10. memcoai-0.1.0/docs/conf.py +97 -0
  11. memcoai-0.1.0/docs/errors.rst +7 -0
  12. memcoai-0.1.0/docs/index.rst +43 -0
  13. memcoai-0.1.0/docs/operations.rst +37 -0
  14. memcoai-0.1.0/docs/types.rst +10 -0
  15. memcoai-0.1.0/examples/README.md +32 -0
  16. memcoai-0.1.0/examples/concurrent_searches_async.py +59 -0
  17. memcoai-0.1.0/examples/contribute.py +69 -0
  18. memcoai-0.1.0/examples/handling_errors.py +154 -0
  19. memcoai-0.1.0/examples/import_memories.py +85 -0
  20. memcoai-0.1.0/examples/langchain_agent.py +126 -0
  21. memcoai-0.1.0/examples/quickstart.py +35 -0
  22. memcoai-0.1.0/examples/quickstart_async.py +37 -0
  23. memcoai-0.1.0/examples/search_and_rate.py +68 -0
  24. memcoai-0.1.0/memcoai/SDK_PROVENANCE.yaml +29 -0
  25. memcoai-0.1.0/memcoai/__init__.py +94 -0
  26. memcoai-0.1.0/memcoai/_aio.py +348 -0
  27. memcoai-0.1.0/memcoai/_auth.py +154 -0
  28. memcoai-0.1.0/memcoai/_channel.py +166 -0
  29. memcoai-0.1.0/memcoai/_config.py +299 -0
  30. memcoai-0.1.0/memcoai/_convert.py +451 -0
  31. memcoai-0.1.0/memcoai/_deprecation.py +73 -0
  32. memcoai-0.1.0/memcoai/_limits.py +63 -0
  33. memcoai-0.1.0/memcoai/_logging.py +250 -0
  34. memcoai-0.1.0/memcoai/_provenance.py +253 -0
  35. memcoai-0.1.0/memcoai/_requests.py +519 -0
  36. memcoai-0.1.0/memcoai/_sync.py +256 -0
  37. memcoai-0.1.0/memcoai/_validate.py +407 -0
  38. memcoai-0.1.0/memcoai/agent.py +1283 -0
  39. memcoai-0.1.0/memcoai/errors.py +480 -0
  40. memcoai-0.1.0/memcoai/memory/__init__.py +0 -0
  41. memcoai-0.1.0/memcoai/memory/tools.json +163 -0
  42. memcoai-0.1.0/memcoai/memory/v1/__init__.py +0 -0
  43. memcoai-0.1.0/memcoai/memory/v1/memory_pb2.py +101 -0
  44. memcoai-0.1.0/memcoai/memory/v1/memory_pb2.pyi +381 -0
  45. memcoai-0.1.0/memcoai/memory/v1/memory_pb2_grpc.py +494 -0
  46. memcoai-0.1.0/memcoai/memory/v1/memory_pb2_grpc.pyi +268 -0
  47. memcoai-0.1.0/memcoai/operations.py +2060 -0
  48. memcoai-0.1.0/memcoai/py.typed +0 -0
  49. memcoai-0.1.0/memcoai/types.py +822 -0
  50. memcoai-0.1.0/pyproject.toml +187 -0
  51. memcoai-0.1.0/requirements.txt +2 -0
  52. memcoai-0.1.0/systemtest/__init__.py +1 -0
  53. memcoai-0.1.0/systemtest/conftest.py +109 -0
  54. memcoai-0.1.0/systemtest/test_lifecycle.py +321 -0
  55. memcoai-0.1.0/tests/__init__.py +1 -0
  56. memcoai-0.1.0/tests/conftest.py +93 -0
  57. memcoai-0.1.0/tests/fake_server.py +181 -0
  58. memcoai-0.1.0/tests/test_agent.py +544 -0
  59. memcoai-0.1.0/tests/test_async_client.py +207 -0
  60. memcoai-0.1.0/tests/test_channel.py +82 -0
  61. memcoai-0.1.0/tests/test_client.py +335 -0
  62. memcoai-0.1.0/tests/test_config.py +77 -0
  63. memcoai-0.1.0/tests/test_convert.py +297 -0
  64. memcoai-0.1.0/tests/test_deprecation_and_limits.py +547 -0
  65. memcoai-0.1.0/tests/test_docstring_examples.py +95 -0
  66. memcoai-0.1.0/tests/test_errors.py +103 -0
  67. memcoai-0.1.0/tests/test_examples.py +33 -0
  68. memcoai-0.1.0/tests/test_langchain_example.py +200 -0
  69. memcoai-0.1.0/tests/test_llms_txt.py +678 -0
  70. memcoai-0.1.0/tests/test_logging.py +198 -0
  71. memcoai-0.1.0/tests/test_memory_feedback.py +241 -0
  72. memcoai-0.1.0/tests/test_parity.py +192 -0
  73. memcoai-0.1.0/tests/test_provenance.py +198 -0
  74. memcoai-0.1.0/tests/test_regressions.py +715 -0
  75. memcoai-0.1.0/tests/test_retries.py +121 -0
  76. memcoai-0.1.0/tests/test_session_scope.py +152 -0
  77. memcoai-0.1.0/tests/test_tool_copy.py +468 -0
  78. memcoai-0.1.0/tests/test_validate.py +175 -0
  79. memcoai-0.1.0/uv.lock +2982 -0
@@ -0,0 +1,78 @@
1
+ # Root-only entries are anchored with a leading "/". An unanchored pattern
2
+ # matches at ANY depth and will silently swallow nested directories.
3
+
4
+ # --- Secrets / local config ---
5
+ .env
6
+ .env.*
7
+ !.env.example
8
+
9
+ # --- OS / editor cruft ---
10
+ .DS_Store
11
+ Thumbs.db
12
+ *.swp
13
+ *~
14
+ /.vscode/
15
+
16
+ # --- JetBrains IDEs ---
17
+ /.idea/
18
+ *.iml
19
+ *.iws
20
+ /out/
21
+ .editorconfig
22
+
23
+ # --- Python ---
24
+ __pycache__/
25
+ *.py[cod]
26
+ *.egg-info/
27
+ .eggs/
28
+ .venv/
29
+ .venv-*/
30
+ venv/
31
+ .pytest_cache/
32
+ .mypy_cache/
33
+ .ruff_cache/
34
+ .tox/
35
+ .nox/
36
+ .hypothesis/
37
+ .coverage
38
+ .coverage.*
39
+ coverage.xml
40
+ htmlcov/
41
+
42
+ # --- Node.js ---
43
+ node_modules/
44
+ .npm/
45
+ .yarn/cache/
46
+ .pnp.*
47
+ .eslintcache
48
+ *.tsbuildinfo
49
+ npm-debug.log*
50
+ yarn-debug.log*
51
+ yarn-error.log*
52
+ pnpm-debug.log*
53
+ # `npm pack` writes the tarball beside package.json.
54
+ *.tgz
55
+ *.tar.gz
56
+
57
+ # --- Go ---
58
+ # go.work resolves nested modules locally, which hides breakage in consumers
59
+ # and Docker builds that have no workspace. Keep it local-only.
60
+ go.work
61
+ go.work.sum
62
+ *.test
63
+ *.exe
64
+ *.dll
65
+ *.dylib
66
+ *.so
67
+ coverage.out
68
+
69
+ # --- Shared build output (Python sdists/wheels, Node bundles, Sphinx) ---
70
+ build/
71
+ _build/
72
+ dist/
73
+ coverage/
74
+
75
+ # NOTE: generated gRPC/protobuf client code (*.pb.go, *_pb2.py, *_pb2_grpc.py,
76
+ # *_pb.js, *_pb.d.ts) is intentionally NOT ignored. This repo publishes those
77
+ # clients; a `go get` / `pip install` consumer cannot run codegen.
78
+
@@ -0,0 +1 @@
1
+ 3.10
memcoai-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Memco Labs, 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.
memcoai-0.1.0/Makefile ADDED
@@ -0,0 +1,99 @@
1
+ # Python SDK tasks.
2
+ #
3
+ # Implements the target names the root Makefile fans out to, so `make lint` at
4
+ # the repository root reaches this file. Run it directly for anything
5
+ # Python-specific: `make -C python docs-serve`.
6
+
7
+ .DEFAULT_GOAL := help
8
+ UV ?= uv
9
+
10
+ # Every `uv run` re-syncs the environment to exactly the extras it names, so a
11
+ # target asking for fewer than another evicts that other's tools. Worse, uv
12
+ # recreates the environment outright when the interpreter it resolves differs
13
+ # from the current one. Asking for the same extras everywhere means any
14
+ # recreate restores a complete environment rather than a partial one.
15
+ EXTRAS := --extra dev --extra docs
16
+ RUN := $(UV) run $(EXTRAS)
17
+
18
+ .PHONY: help install lint format typecheck test test-all system-test coverage docs docs-serve build clean
19
+
20
+ help: ## Show this help
21
+ @grep -hE '^[a-z][a-z-]*:.*?## ' $(MAKEFILE_LIST) \
22
+ | awk -F':.*?## ' '{printf " \033[36m%-12s\033[0m %s\n", $$1, $$2}'
23
+
24
+ install: ## Create the dev environment (floor version, per .python-version)
25
+ $(UV) sync $(EXTRAS)
26
+
27
+ lint: ## Check formatting and lint rules
28
+ $(RUN) ruff check .
29
+ $(RUN) ruff format --check .
30
+
31
+ format: ## Apply formatting and safe lint fixes
32
+ $(RUN) ruff check --fix .
33
+ $(RUN) ruff format .
34
+
35
+ typecheck: ## Strict type check of the SDK and tests
36
+ $(RUN) mypy
37
+
38
+ test: ## Run the test suite
39
+ $(RUN) pytest -q
40
+
41
+ coverage: ## Run the suite and report coverage (fails below the floor)
42
+ $(RUN) pytest -q --cov --cov-report=term --cov-report=html --cov-report=xml
43
+ @echo "open python/htmlcov/index.html"
44
+
45
+ # Not collected by `test`: pyproject's testpaths names tests/ only, so the live
46
+ # suite is reached solely through this target. It needs MEMCO_API_TOKEN and a
47
+ # reachable service; without one it reports itself skipped rather than failing.
48
+ system-test: ## Run the live suite against the real service
49
+ $(RUN) pytest -q systemtest
50
+
51
+ test-all: ## Run the suite on every supported interpreter
52
+ @for v in 3.10 3.11 3.12 3.13; do \
53
+ echo "--- python $$v ---"; \
54
+ UV_PROJECT_ENVIRONMENT=.venv-$$v $(UV) run --python $$v $(EXTRAS) pytest -q \
55
+ || exit 1; \
56
+ done
57
+
58
+ # The doctree cache is written beside the HTML rather than inside it: the
59
+ # output directory is published verbatim as `memco-docs-python-<version>.tar.gz`,
60
+ # and Sphinx's default `<outdir>/.doctrees` would put megabytes of pickles on
61
+ # the documentation site.
62
+ #
63
+ # Both output directories are emptied first. Sphinx never removes output, so a
64
+ # renamed or deleted page lingers in docs/_build/html for as long as the tree
65
+ # does — and the assembler below requires every page the build published to be
66
+ # reachable from llms.txt, so it would rightly refuse a tree carrying a page
67
+ # the sources no longer describe. This is what typedoc.json's cleanOutputDir
68
+ # does on the Node side, and what a fresh CI checkout gets for free. The
69
+ # doctree caches survive it, so a rebuild is still incremental.
70
+ #
71
+ # The markdown pass carries a doctree cache of its own: napoleon is configured
72
+ # differently for it, and a config change invalidates the whole cache, so a
73
+ # shared one would be thrown away and rebuilt on every alternating run. Those
74
+ # two settings are the difference. sphinx-markdown-builder renders no
75
+ # admonition that holds a doctest block, so with conf.py's `Example:` handling
76
+ # left as it is, every worked example — the most useful thing in the file an
77
+ # agent reads — is dropped with a warning. Rendering them as a plain section
78
+ # keeps them, and costs the HTML nothing, which is built without the override.
79
+ docs: ## Build the reference documentation (warnings are errors)
80
+ rm -rf docs/_build/html docs/_build/markdown
81
+ $(RUN) sphinx-build -b html -W --keep-going -d docs/_build/doctrees docs docs/_build/html
82
+ $(RUN) sphinx-build -b markdown -W --keep-going \
83
+ -D napoleon_use_admonition_for_examples=0 \
84
+ -D napoleon_use_admonition_for_notes=0 \
85
+ -d docs/_build/doctrees-markdown docs docs/_build/markdown
86
+ python3 ../scripts/build_llms_txt.py python
87
+ @echo "open python/docs/_build/html/index.html"
88
+
89
+ docs-serve: docs ## Build the reference and serve it locally
90
+ $(RUN) python -m http.server -d docs/_build/html 8000
91
+
92
+ build: ## Build the sdist and the wheel
93
+ rm -rf dist
94
+ $(UV) build
95
+
96
+ clean: ## Remove build and cache artefacts
97
+ rm -rf dist docs/_build htmlcov coverage.xml .coverage .venv-3.* \
98
+ .mypy_cache .ruff_cache .pytest_cache
99
+ find . -name __pycache__ -type d -prune -exec rm -rf {} +
memcoai-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,395 @@
1
+ Metadata-Version: 2.5
2
+ Name: memcoai
3
+ Version: 0.1.0
4
+ Summary: Python SDK for Memco Shared Memory.
5
+ Project-URL: Homepage, https://memco.ai
6
+ Project-URL: Documentation, https://docs.memco.ai
7
+ Project-URL: Source, https://github.com/memcoai/memcoai
8
+ Project-URL: Issues, https://github.com/memcoai/memcoai/issues
9
+ Author: Memco Labs, Inc.
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Requires-Python: >=3.10
13
+ Requires-Dist: googleapis-common-protos<2,>=1.60.0
14
+ Requires-Dist: grpcio-health-checking<2,>=1.76.0
15
+ Requires-Dist: grpcio-status<2,>=1.76.0
16
+ Requires-Dist: grpcio<2,>=1.76.0
17
+ Requires-Dist: protobuf<8,>=6.31.1
18
+ Provides-Extra: dev
19
+ Requires-Dist: ddgs>=9; extra == 'dev'
20
+ Requires-Dist: langchain-google-genai>=4; extra == 'dev'
21
+ Requires-Dist: langchain>=1.0; extra == 'dev'
22
+ Requires-Dist: mypy>=1.11; extra == 'dev'
23
+ Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
24
+ Requires-Dist: pytest-cov>=5; extra == 'dev'
25
+ Requires-Dist: pytest>=8; extra == 'dev'
26
+ Requires-Dist: ruff>=0.6; extra == 'dev'
27
+ Requires-Dist: types-protobuf; extra == 'dev'
28
+ Provides-Extra: docs
29
+ Requires-Dist: furo>=2024.1; extra == 'docs'
30
+ Requires-Dist: sphinx-markdown-builder>=0.6.8; extra == 'docs'
31
+ Requires-Dist: sphinx>=7.3; extra == 'docs'
32
+ Description-Content-Type: text/markdown
33
+
34
+ <p align="center">
35
+ <picture>
36
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/memcoai/memcoai/main/assets/logo-dark.svg">
37
+ <img alt="Memco" src="https://raw.githubusercontent.com/memcoai/memcoai/main/assets/logo.svg" width="320">
38
+ </picture>
39
+ </p>
40
+
41
+ <p align="center">
42
+ Python SDK for <b>Memco Shared Memory</b>.<br>
43
+ <a href="https://memco.ai">memco.ai</a> &middot;
44
+ <a href="https://docs.memco.ai">docs.memco.ai</a>
45
+ </p>
46
+
47
+ <p align="center">
48
+ <a href="https://github.com/memcoai/memcoai/actions/workflows/ci_python.yaml"><img alt="CI (Python)" src="https://github.com/memcoai/memcoai/actions/workflows/ci_python.yaml/badge.svg?branch=main"></a>
49
+ <img alt="Python versions" src="https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12%20%7C%203.13-3775a9"><br>
50
+ <img alt="Coverage" src="https://img.shields.io/badge/coverage-%E2%89%A595%25-brightgreen">
51
+ <a href="LICENSE"><img alt="Licence" src="https://img.shields.io/badge/licence-MIT-blue"></a>
52
+ </p>
53
+
54
+ ---
55
+
56
+ Memco Shared Memory is a persistent, searchable memory that your team and its AI
57
+ agents share. An agent searches it before starting work and writes back what it
58
+ learned when it finishes, so what one agent establishes, every teammate's agent
59
+ can find.
60
+
61
+ This package wraps the generated gRPC client with connection handling,
62
+ credential management, typed results and typed errors. Every operation is
63
+ available synchronously and asynchronously, and the package is fully typed —
64
+ `py.typed` ships, so mypy and pyright check your calls.
65
+
66
+ **You need an account and an API key.** Create one at [memco.ai](https://memco.ai).
67
+
68
+ ## Install
69
+
70
+ ```bash
71
+ pip install memcoai
72
+ ```
73
+
74
+ Requires Python 3.10 or newer.
75
+
76
+ ## Quick start
77
+
78
+ ```python
79
+ from memcoai import Memco
80
+
81
+ with Memco() as client: # reads MEMCO_API_TOKEN
82
+ for domain in client.memory.list_domains().domains:
83
+ print(domain.slug, "-", domain.summary)
84
+
85
+ session = client.memory.start_session("coding")
86
+
87
+ result = session.search("how should a client authenticate against the memory API")
88
+ for memory in result.memories:
89
+ for insight in memory.insights:
90
+ print(insight.title, insight.updated)
91
+ ```
92
+
93
+ `start_session` returns a session with every session-bound operation already
94
+ applied — `search`, `share_feedback`, and the rest — so nothing above threads
95
+ an id through a call by hand. That matters because a call that silently drops
96
+ the id is still a valid call — it just stops being part of the series that
97
+ relates one task's work, which is the kind of mistake an agent makes and
98
+ nobody notices. `with_session` opens the same kind of session as a context
99
+ manager, for wherever that scoping reads better, and a single result rates
100
+ itself directly with `feedback`:
101
+
102
+ ```python
103
+ with client.memory.with_session("coding") as session:
104
+ result = session.search("how should a client authenticate")
105
+ result.memories[0].feedback(relevant=True, correct=True)
106
+ ```
107
+
108
+ Rating more than one result at once still goes through `share_feedback`
109
+ directly, with a `FeedbackRating` per result:
110
+
111
+ ```python
112
+ from memcoai.types import FeedbackRating
113
+
114
+ session.share_feedback(
115
+ feedback=[
116
+ FeedbackRating(idx=memory.idx, relevant=True, correct=True) for memory in result.memories
117
+ ]
118
+ )
119
+ ```
120
+
121
+ Everything works asynchronously too, with the same method names:
122
+
123
+ ```python
124
+ from memcoai import AsyncMemco
125
+
126
+ async with AsyncMemco() as client:
127
+ async with client.memory.with_session("coding") as session:
128
+ result = await session.search("how should a client authenticate")
129
+ ```
130
+
131
+ Runnable programs covering the common workflows are in
132
+ [`examples/`](https://github.com/memcoai/memcoai/blob/main/python/examples/).
133
+
134
+ ## Agents
135
+
136
+ Handing these operations to an LLM takes more than the calls: text telling a
137
+ model what each tool does and what to pass it, a JSON Schema for the arguments,
138
+ results rendered as text it can read, and the handover to whatever is driving
139
+ the loop. The SDK supplies all of it, from the session the tools are bound to:
140
+
141
+ ```python
142
+ from memcoai import Memco, agent
143
+
144
+ with Memco() as client:
145
+ entry = next(d for d in client.memory.list_domains().domains if d.slug == "coding")
146
+
147
+ with client.memory.with_session("coding") as session:
148
+ toolset = session.tools()
149
+
150
+ create_agent( # LangChain
151
+ model,
152
+ tools=toolset.to_langchain(),
153
+ system_prompt=agent.briefing(entry, session.instructions),
154
+ )
155
+ ```
156
+
157
+ The same toolset speaks the other shapes, and runs what a model asks for when
158
+ you are driving the loop yourself:
159
+
160
+ ```python
161
+ response = anthropic.messages.create(tools=toolset.to_anthropic(), ...)
162
+ result = toolset.call(block.name, block.input) # -> text for the model
163
+
164
+ completion = openai.chat.completions.create(tools=toolset.to_openai(), ...)
165
+ result = toolset.call(call.function.name, call.function.arguments) # JSON text is fine
166
+ ```
167
+
168
+ `to_langchain` needs LangChain installed; the other two are plain data and need
169
+ nothing. `AsyncMemco`'s session has the same `tools()`, awaitable and described
170
+ identically.
171
+
172
+ The description a model reads for each tool is the service's, not this SDK's. It
173
+ arrives with every export as `memcoai/memory/tools.json` — the same copy the hosted
174
+ MCP server publishes — and is written into the docstrings the toolset is built
175
+ from, so a change of wording reaches you with a release rather than silently.
176
+
177
+ Three parameters keep their own wording: `tags`, `feedback` and `source` are
178
+ typed objects here and XML strings over MCP, so the service's copy would
179
+ describe an encoding these schemas reject. The session and its domain are bound,
180
+ so there is no `list_domains` or `start_session` tool either — `agent.briefing()`
181
+ says so, since the service's copy mentions both.
182
+
183
+ Every tool is bound to the session it was built from, so nothing a model sends
184
+ can change which session a call is recorded under, and arguments are validated
185
+ before anything is sent. A malformed request, an invented tool name, and a
186
+ handle that resolves to nothing all come back as text the model can act on —
187
+ `agent.AGENT_RECOVERABLE` is where that line is drawn. Everything else, a
188
+ rejected credential above all, is raised: no wording a model reads will fix it.
189
+
190
+ [`examples/langchain_agent.py`](https://github.com/memcoai/memcoai/blob/main/python/examples/langchain_agent.py)
191
+ is a complete agent in eighty lines, and defines no helpers of its own.
192
+
193
+ ## Configuration
194
+
195
+ Arguments win over the environment, which wins over the defaults.
196
+
197
+ | Setting | Argument | Environment | Default |
198
+ |---|---|---|---|
199
+ | Credential | `token` | `MEMCO_API_TOKEN` | required |
200
+ | Endpoint | `host` | `MEMCO_API_HOST` | `grpc.memco.ai:443` |
201
+ | TLS | `tls` | — | `True` |
202
+ | Deadline | `timeout` | — | 30 seconds |
203
+ | Log level | `log_level` | `MEMCO_LOG` | `info` |
204
+
205
+ The credential is either a Memco API key or a session token issued for your
206
+ account; both go in the same header. `MEMCO_API_KEY` is still honoured but warns.
207
+
208
+ Constructing a `Memco` makes two calls. It probes the service's health
209
+ endpoint, so a bad host, port or TLS setting fails immediately rather than on
210
+ your first call; that probe carries no credential, so it cannot check one.
211
+ It then calls `list_domains`, which does — a bad token fails here too — and
212
+ which reports the input limits the service enforces. The client keeps those and
213
+ applies them from then on, so an oversized field is refused locally instead of
214
+ costing a round trip.
215
+
216
+ `AsyncMemco` cannot do any of this in `__init__` — it runs both on `connect()`,
217
+ which `async with` calls for you.
218
+
219
+ ## Logging
220
+
221
+ Everything the SDK logs goes to a logger under `memcoai` — `memcoai._sync`,
222
+ `memcoai._channel`, `memcoai._config` and so on — so configuring that one name
223
+ governs all of it, while a single noisy area can still be quietened on its own:
224
+
225
+ ```python
226
+ logging.getLogger("memcoai").setLevel(logging.WARNING)
227
+ logging.getLogger("memcoai._channel").setLevel(logging.ERROR)
228
+ ```
229
+
230
+ The SDK configures itself at `INFO` when you import it: a line when a client
231
+ connects, a line when it closes, and a rejected credential reported before it is
232
+ raised, since a client is often built somewhere the traceback does not reach.
233
+
234
+ ```console
235
+ $ python app.py
236
+ 2026-08-31 10:02:11,604 memcoai._sync INFO connected to grpc.memco.ai:443 (tls=True)
237
+ 2026-08-31 10:02:14,318 memcoai._sync INFO closed connection to grpc.memco.ai:443
238
+ ```
239
+
240
+ Set `MEMCO_LOG` to change that level — `debug`, `info`, `warning`, `error`,
241
+ `critical`, or `none` to turn it off — or pass `log_level` to either client,
242
+ which wins over the variable:
243
+
244
+ ```python
245
+ with Memco(log_level="debug") as client: # or log_level=logging.DEBUG
246
+ ...
247
+ ```
248
+
249
+ `debug` adds where your credential and endpoint came from, every RPC with its
250
+ outcome and duration, and — the one thing nothing else reveals — when a service
251
+ cap silently trimmed a list you passed:
252
+
253
+ ```console
254
+ $ MEMCO_LOG=debug python app.py
255
+ 2026-08-31 10:02:11,417 memcoai._config DEBUG credential taken from MEMCO_API_TOKEN
256
+ 2026-08-31 10:02:11,417 memcoai._config DEBUG endpoint grpc.memco.ai:443 tls=True (host from the default)
257
+ 2026-08-31 10:02:11,502 memcoai._sync DEBUG health check on grpc.memco.ai:443 ok in 84ms
258
+ 2026-08-31 10:02:11,604 memcoai._sync DEBUG ListDomains ok in 101ms
259
+ 2026-08-31 10:02:11,604 memcoai._sync INFO connected to grpc.memco.ai:443 (tls=True)
260
+ 2026-08-31 10:02:11,731 memcoai._validate DEBUG tags trimmed from 62 to 50 by the service's cap
261
+ 2026-08-31 10:02:11,905 memcoai._sync DEBUG Search ok in 173ms
262
+ ```
263
+
264
+ **No credential is ever written to a record**, at any level.
265
+
266
+ ### If your application configures its own logging
267
+
268
+ The SDK owns its output by default: it attaches a stderr handler to `memcoai` and
269
+ stops that logger propagating, so records go to the SDK's handler and no longer
270
+ reach the ones you attached further up, the root logger's included. That is what
271
+ makes it work with no setup — and it is the wrong shape for an application with
272
+ its own logging, where a redaction filter or log shipper on the root logger would
273
+ never see a memcoai record.
274
+
275
+ To take the SDK's records back into your own pipeline, set `MEMCO_LOG=none` in
276
+ the environment and configure the `memcoai` logger yourself:
277
+
278
+ ```python
279
+ import logging
280
+ from memcoai import Memco # with MEMCO_LOG=none set
281
+
282
+ logging.basicConfig(level=logging.INFO)
283
+ logging.getLogger("memcoai").setLevel(logging.INFO)
284
+
285
+ with Memco() as client: # records flow through your handlers
286
+ ...
287
+ ```
288
+
289
+ ```console
290
+ $ MEMCO_LOG=none python app.py
291
+ INFO:memcoai._sync:connected to grpc.memco.ai:443 (tls=True)
292
+ ```
293
+
294
+ Use the environment variable rather than `log_level="none"` for this: the
295
+ variable is applied when `memcoai` is imported, while the argument is applied
296
+ inside the constructor — after which the level you set is gone and the
297
+ `connected` record has already been written.
298
+
299
+ Both settings are process-wide, because a logger is: two clients asking for
300
+ different levels means the last one constructed decides. An unrecognised
301
+ `MEMCO_LOG` warns and falls back to the default rather than failing the program;
302
+ an unrecognised `log_level` raises `MemcoConfigError`, because an argument is
303
+ your own code rather than a stray variable in the environment.
304
+
305
+ ## Operations
306
+
307
+ The memory operations live on `client.memory`.
308
+
309
+ | Method | Purpose |
310
+ |---|---|
311
+ | `memory.list_domains()` | Which domains this credential may name, and their tag vocabulary |
312
+ | `memory.start_session(domain)` | Open a session, with every session-bound operation already applied |
313
+ | `memory.search(query, ...)` | Find memories answering a task-based query |
314
+ | `memory.get_memory(idx)` | Fetch a memory a search returned only as a reference |
315
+ | `memory.create_memory(...)` | Save new knowledge |
316
+ | `memory.enrich_memory(...)` | Add to a memory a search returned, or open a new one |
317
+ | `memory.share_feedback(...)` | Rate the results of one search |
318
+ | `memory.search(...).memories[0].feedback(...)` | Rate a single result directly, no `FeedbackRating` needed |
319
+ | `memory.revert_memory(operation_id)` | Undo one of your own writes |
320
+ | `memory.import_memories(memories, ...)` | Contribute many memories at once, splitting the batch as the service requires |
321
+
322
+ ## Errors
323
+
324
+ Every failure is a subclass of `MemcoError`, so no raw `grpc.RpcError` ever
325
+ reaches you.
326
+
327
+ ```
328
+ MemcoError
329
+ ├── MemcoConfigError bad configuration; no request was sent
330
+ └── MemcoAPIError the service returned an error status
331
+ ├── MemcoAuthenticationError credential missing, expired or rejected
332
+ ├── MemcoPermissionError credential lacks the scope or role
333
+ ├── MemcoInvalidRequestError malformed request
334
+ ├── MemcoNotFoundError handle resolved to nothing visible
335
+ ├── MemcoPreconditionFailedError a precondition is unmet
336
+ │ └── MemcoSunsetError past its sunset; carries .kind
337
+ ├── MemcoResourceExhaustedError rate limit or quota; carries .kind
338
+ ├── MemcoUnavailableError service unreachable
339
+ │ └── MemcoUnhealthyError reachable, but reporting not-serving
340
+ ├── MemcoTimeoutError deadline exceeded
341
+ └── MemcoInternalError everything else
342
+ ```
343
+
344
+ Two behaviours worth knowing:
345
+
346
+ **Some limits are checked locally.** Oversized fields and missing argument
347
+ combinations raise `MemcoInvalidRequestError` before any request is sent, so a
348
+ malformed call costs no round trip.
349
+
350
+ **Not every "not found" is an error.** `revert_memory` reports a missing,
351
+ expired or moderated operation through `RevertResult.outcome` rather than
352
+ raising, because each describes state you can act on rather than a failure.
353
+
354
+ ```python
355
+ result = client.memory.revert_memory("create-8fj2k1")
356
+ if result.outcome is RevertOutcome.EXPIRED:
357
+ print("outside the revert window")
358
+ ```
359
+
360
+ [`examples/handling_errors.py`](https://github.com/memcoai/memcoai/blob/main/python/examples/handling_errors.py) works through every
361
+ failure mode and what to do about each.
362
+
363
+ ## Provenance
364
+
365
+ The package records which version of the service contract its generated client
366
+ was built from:
367
+
368
+ ```python
369
+ from memcoai import provenance
370
+
371
+ provenance().server_commit # the commit this wheel was built from
372
+ provenance().protos[0].path # 'memcoai/memory/v1/memory.proto'
373
+ ```
374
+
375
+ ## Contributing
376
+
377
+ Bug reports and pull requests are welcome, and you do not need access to Memco's
378
+ servers to work on this — the test suite runs against an in-process gRPC server,
379
+ so everything passes offline. The
380
+ [Python section of CONTRIBUTING.md](https://github.com/memcoai/memcoai/blob/main/CONTRIBUTING.md#python)
381
+ covers local setup, the checks, and the test conventions.
382
+
383
+ ```bash
384
+ make install # from the repository root
385
+ make check # lint, typecheck, test, docs — everything CI runs
386
+ make -C python docs-serve # build the reference and read it locally
387
+ ```
388
+
389
+ The generated client under `memcoai/memory/` is produced from the service
390
+ contract and is replaced wholesale when regenerated; everything else in
391
+ `memcoai/` is hand-written.
392
+
393
+ ## Licence
394
+
395
+ [MIT](https://github.com/memcoai/memcoai/blob/main/LICENSE) &copy; Memco Labs, Inc.