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.
- memcoai-0.1.0/.gitignore +78 -0
- memcoai-0.1.0/.python-version +1 -0
- memcoai-0.1.0/LICENSE +21 -0
- memcoai-0.1.0/Makefile +99 -0
- memcoai-0.1.0/PKG-INFO +395 -0
- memcoai-0.1.0/README.md +362 -0
- memcoai-0.1.0/deps/sdk.txt +12 -0
- memcoai-0.1.0/docs/agent.rst +10 -0
- memcoai-0.1.0/docs/clients.rst +18 -0
- memcoai-0.1.0/docs/conf.py +97 -0
- memcoai-0.1.0/docs/errors.rst +7 -0
- memcoai-0.1.0/docs/index.rst +43 -0
- memcoai-0.1.0/docs/operations.rst +37 -0
- memcoai-0.1.0/docs/types.rst +10 -0
- memcoai-0.1.0/examples/README.md +32 -0
- memcoai-0.1.0/examples/concurrent_searches_async.py +59 -0
- memcoai-0.1.0/examples/contribute.py +69 -0
- memcoai-0.1.0/examples/handling_errors.py +154 -0
- memcoai-0.1.0/examples/import_memories.py +85 -0
- memcoai-0.1.0/examples/langchain_agent.py +126 -0
- memcoai-0.1.0/examples/quickstart.py +35 -0
- memcoai-0.1.0/examples/quickstart_async.py +37 -0
- memcoai-0.1.0/examples/search_and_rate.py +68 -0
- memcoai-0.1.0/memcoai/SDK_PROVENANCE.yaml +29 -0
- memcoai-0.1.0/memcoai/__init__.py +94 -0
- memcoai-0.1.0/memcoai/_aio.py +348 -0
- memcoai-0.1.0/memcoai/_auth.py +154 -0
- memcoai-0.1.0/memcoai/_channel.py +166 -0
- memcoai-0.1.0/memcoai/_config.py +299 -0
- memcoai-0.1.0/memcoai/_convert.py +451 -0
- memcoai-0.1.0/memcoai/_deprecation.py +73 -0
- memcoai-0.1.0/memcoai/_limits.py +63 -0
- memcoai-0.1.0/memcoai/_logging.py +250 -0
- memcoai-0.1.0/memcoai/_provenance.py +253 -0
- memcoai-0.1.0/memcoai/_requests.py +519 -0
- memcoai-0.1.0/memcoai/_sync.py +256 -0
- memcoai-0.1.0/memcoai/_validate.py +407 -0
- memcoai-0.1.0/memcoai/agent.py +1283 -0
- memcoai-0.1.0/memcoai/errors.py +480 -0
- memcoai-0.1.0/memcoai/memory/__init__.py +0 -0
- memcoai-0.1.0/memcoai/memory/tools.json +163 -0
- memcoai-0.1.0/memcoai/memory/v1/__init__.py +0 -0
- memcoai-0.1.0/memcoai/memory/v1/memory_pb2.py +101 -0
- memcoai-0.1.0/memcoai/memory/v1/memory_pb2.pyi +381 -0
- memcoai-0.1.0/memcoai/memory/v1/memory_pb2_grpc.py +494 -0
- memcoai-0.1.0/memcoai/memory/v1/memory_pb2_grpc.pyi +268 -0
- memcoai-0.1.0/memcoai/operations.py +2060 -0
- memcoai-0.1.0/memcoai/py.typed +0 -0
- memcoai-0.1.0/memcoai/types.py +822 -0
- memcoai-0.1.0/pyproject.toml +187 -0
- memcoai-0.1.0/requirements.txt +2 -0
- memcoai-0.1.0/systemtest/__init__.py +1 -0
- memcoai-0.1.0/systemtest/conftest.py +109 -0
- memcoai-0.1.0/systemtest/test_lifecycle.py +321 -0
- memcoai-0.1.0/tests/__init__.py +1 -0
- memcoai-0.1.0/tests/conftest.py +93 -0
- memcoai-0.1.0/tests/fake_server.py +181 -0
- memcoai-0.1.0/tests/test_agent.py +544 -0
- memcoai-0.1.0/tests/test_async_client.py +207 -0
- memcoai-0.1.0/tests/test_channel.py +82 -0
- memcoai-0.1.0/tests/test_client.py +335 -0
- memcoai-0.1.0/tests/test_config.py +77 -0
- memcoai-0.1.0/tests/test_convert.py +297 -0
- memcoai-0.1.0/tests/test_deprecation_and_limits.py +547 -0
- memcoai-0.1.0/tests/test_docstring_examples.py +95 -0
- memcoai-0.1.0/tests/test_errors.py +103 -0
- memcoai-0.1.0/tests/test_examples.py +33 -0
- memcoai-0.1.0/tests/test_langchain_example.py +200 -0
- memcoai-0.1.0/tests/test_llms_txt.py +678 -0
- memcoai-0.1.0/tests/test_logging.py +198 -0
- memcoai-0.1.0/tests/test_memory_feedback.py +241 -0
- memcoai-0.1.0/tests/test_parity.py +192 -0
- memcoai-0.1.0/tests/test_provenance.py +198 -0
- memcoai-0.1.0/tests/test_regressions.py +715 -0
- memcoai-0.1.0/tests/test_retries.py +121 -0
- memcoai-0.1.0/tests/test_session_scope.py +152 -0
- memcoai-0.1.0/tests/test_tool_copy.py +468 -0
- memcoai-0.1.0/tests/test_validate.py +175 -0
- memcoai-0.1.0/uv.lock +2982 -0
memcoai-0.1.0/.gitignore
ADDED
|
@@ -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> ·
|
|
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) © Memco Labs, Inc.
|