Repository navigation
Expand file tree
/
Copy pathpyproject.toml
More file actions
907 lines (841 loc) · 34.7 KB
/
Copy pathpyproject.toml
File metadata and controls
907 lines (841 loc) · 34.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "locus-sdk"
version = "5.3.2"
description = "Oracle Generative AI Multi-Agent Reasoning Orchestrator SDK: the agent framework where the model never holds the trigger — every consequential action clears your policy first, waits for a human when it matters, and lands on a record you can verify. Build on it, or put it around the agent you already have."
readme = "README.md"
license = "UPL-1.0"
license-files = ["LICENSE", "LICENSE.txt", "THIRD_PARTY_LICENSES.txt"]
requires-python = ">=3.11"
authors = [
{ name = "Oracle and/or its affiliates" },
]
# Ordered by the job a searcher is actually trying to do. The gate terms come
# first on purpose: people arrive looking for "how do I stop my agent doing X
# in production", not for another agent framework.
keywords = [
"human-in-the-loop",
"hitl",
"agent-approvals",
"tool-approval",
"tool-authorization",
"guardrails",
"audit-trail",
"policy-engine",
"ai-safety",
"agent-security",
"agents",
"agent-framework",
"agentic",
"agentic-harness",
"agent-harness",
"oracle-generative-ai",
"oci-generative-ai",
"oracle-database",
"ai",
"llm",
"multi-agent",
"orchestration",
"cognitive-router",
"rag",
"grounding",
"gsar",
"safety",
"pydantic",
"react",
"reflexion",
]
classifiers = [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"License :: OSI Approved :: Universal Permissive License (UPL)",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Topic :: Scientific/Engineering :: Artificial Intelligence",
"Topic :: Software Development :: Libraries :: Application Frameworks",
"Typing :: Typed",
]
dependencies = [
# Capped at <1.0: httpx 1.0.dev3 drops the top-level ``httpx.Auth``
# re-export, which ``BearerAuth`` (and other auth helpers) subclass.
# Lift the cap after porting those to the 1.0 auth API.
"httpx>=0.27,<1.0",
"pydantic>=2.0",
"pydantic-settings>=2.0",
"typing-extensions>=4.0",
]
[project.optional-dependencies]
# Model providers — every provider class under src/locus/models/native/
# has a matching extra here. Keep this list in sync when adding a provider.
# openai 3.x moved to the httpx2 client; the OCI V1 transport signs requests
# through an httpx.Auth hook, so the SDK stays on the httpx-based 2.x line
# until that transport is ported. The lock resolves to 2.41.
openai = ["openai>=1.50,<3"]
anthropic = ["anthropic>=0.40"]
# OCI Generative AI speaks the OpenAI Chat Completions wire protocol;
# locus.models.providers.oci.openai_compat lazy-imports the `openai`
# client at request time (`client` property). Without it in the extra,
# `pip install "locus-sdk[oci]"` boots fine but every .complete() call
# raises ModuleNotFoundError. Pin alongside the [openai] extra.
# ``requests``: the request signer (locus.models.providers.oci._signing) builds on
# requests.auth.AuthBase; the OCI SDK vendors its own copy, so it must be listed.
oci = ["oci>=2.173.0", "openai>=1.50,<3", "requests>=2.31"]
# All LLM / model providers — short alias for pip discoverability.
llm = [
"openai>=1.50,<3",
"anthropic>=0.40",
"oci>=2.173.0",
"requests>=2.31",
]
# Observability
telemetry = [
"opentelemetry-api>=1.20",
"opentelemetry-sdk>=1.20",
"opentelemetry-exporter-otlp>=1.20",
]
# MCP support
mcp = ["mcp>=1.0"]
# Drop-in HTTP server (FastAPI app for AgentServer + SSE streaming).
# Pulled in production deployments via ``pip install "locus-sdk[server]"``.
server = [
"fastapi>=0.110",
"uvicorn[standard]>=0.27",
]
# Checkpoint backends — one extra per backend under
# src/locus/memory/backends/ that needs a runtime dep. Keep in sync.
redis = ["redis>=5.0"]
postgresql = ["asyncpg>=0.29"]
mysql = ["mysql-connector-python>=9.0"]
opensearch = ["opensearch-py>=2.4"]
# S3-compatible object-storage checkpointer (src/locus/memory/backends/s3.py):
# OCI Object Storage through its S3-compatible API, MinIO, R2, …
s3 = ["boto3>=1.34"]
# Security-domain tooling — red-team probes, SOC triage, intel / SIEM / EDR /
# scanner / fingerprint adapters, OCI cloud posture — is a separate, opt-in
# package in this repo (packages/locus-sdk-security/, imported as ``locus_security``).
# It is not published to PyPI, so there is no extra for it: install it from
# the repository.
# Browser tools (src/locus/tools/browser.py). After installing, fetch a browser
# with `playwright install chromium`.
browser = ["playwright>=1.45"]
# Signed audit trails (src/locus/control/audit.py) — Ed25519 signatures over
# each record so an export verifies from public keys alone. Unsigned trails
# need nothing.
audit = ["cryptography>=42"]
# Durable agent runs on Temporal (src/locus/durable/temporal.py).
temporal = ["temporalio>=1.7"]
# Gemini Live realtime voice sessions (src/locus/voice/gemini.py).
gemini = ["google-genai>=1.30"]
# Durable agent runs on DBOS (src/locus/durable/dbos.py).
dbos = ["dbos>=2.0"]
# NVIDIA OpenShell sandboxes as a coding-harness workspace
# (src/locus/harness/openshell.py). Only the client constructors import it; a
# caller that passes its own client needs nothing from it.
openshell = ["openshell>=0.1.2,<0.2"]
# OCI GenAI Sandboxes as a coding-harness workspace
# (src/locus/harness/oci_sandbox.py). The sandbox client ships in the OCI
# SDK from 2.189.0, imported lazily (``import locus.harness`` does not need it).
oci-sandbox = ["oci>=2.189.0"]
# Mem0 agent-memory manager (src/locus/memory/managers/mem0.py) — the
# portable, self-hostable long-term memory backend.
mem0 = ["mem0ai>=0.1"]
# All checkpoint backends. `pip install locus-sdk[checkpoints]` is the
# one-shot install for durable thread state across every backend.
# Oracle Database 26ai: checkpointers, versioned saver, store, vector store,
# in-DB chunker/embeddings and the ADB loader all ride the thin driver.
oracledb = ["oracledb>=2.2.0"]
# Oracle's official agent-memory client. Used by the
# `OracleAgentMemoryManager` adapter in src/locus/memory/managers/.
# Alpha-grade upstream (Dev Status 3 as of 26.4.0) — pinned to the
# 26.x line so a breaking 27.0 doesn't auto-upgrade through pip.
agentmemory = ["oracleagentmemory>=26.4.0,<27"]
checkpoints = [
"redis>=5.0",
"asyncpg>=0.29",
"mysql-connector-python>=9.0",
"opensearch-py>=2.4",
"boto3>=1.34",
"oracledb>=2.2.0",
"oci>=2.173.0",
"requests>=2.31",
"oracleagentmemory>=26.4.0,<27",
]
# RAG vector stores — one extra per store under src/locus/rag/stores/
# that needs a runtime dep. Keep in sync.
# numpy is PgMemory's HRR encoding: without it rows are stored with no vector
# and recall degrades to a substring match.
pgvector = ["asyncpg>=0.29", "numpy>=1.26"]
qdrant = ["qdrant-client>=1.9"]
chroma = ["chromadb>=0.5"]
# Cohere direct API (rerank + embeddings) and a local cross-encoder
# reranker. Neither requires any cloud vendor SDK.
cohere = ["cohere>=5.0"]
rerank-local = ["sentence-transformers>=3.0"]
rerank = ["cohere>=5.0", "sentence-transformers>=3.0"]
# Full RAG stack — every vector store + embedder + reranker Locus ships.
# Use this if you want to pick a store at runtime rather than up front.
rag = [
"asyncpg>=0.29",
"opensearch-py>=2.4",
"oracledb>=2.2.0",
"oci>=2.173.0",
"requests>=2.31",
"qdrant-client>=1.9",
"chromadb>=0.5",
"openai>=1.50,<3",
"cohere>=5.0",
"sentence-transformers>=3.0",
]
# Full SDK — all providers and backends.
sdk = [
"openai>=1.50,<3",
"anthropic>=0.40",
"cohere>=5.0",
"sentence-transformers>=3.0",
"opentelemetry-api>=1.20",
"opentelemetry-sdk>=1.20",
"opentelemetry-exporter-otlp>=1.20",
"mcp>=1.0",
"fastapi>=0.110",
"uvicorn[standard]>=0.27",
"redis>=5.0",
"asyncpg>=0.29",
"mysql-connector-python>=9.0",
"opensearch-py>=2.4",
"boto3>=1.34",
"qdrant-client>=1.9",
"chromadb>=0.5",
"mem0ai>=0.1",
"oci>=2.173.0",
"requests>=2.31",
"oracledb>=2.2.0",
"oracleagentmemory>=26.4.0,<27",
]
# Documentation site (mkdocs + Material + mkdocstrings). The API reference
# imports the package, so install the SDK alongside: ``pip install -e ".[docs]"``.
docs = [
"mkdocs>=1.6",
"mkdocs-material>=9.5",
"mkdocstrings[python]>=0.27",
]
# Development
dev = [
"hatch>=1.12",
"pre-commit>=3.8",
# Kept in lockstep with .pre-commit-config.yaml (ruff-pre-commit v0.15.0).
# An open `>=` let CI float to 0.16, which formats Markdown code blocks and
# adds rules (ISC004, PLR0917) the tree has never been checked against — so
# a commit green on its PR went red on main with no source change. Bump this
# and the pre-commit rev together, in their own change.
"ruff>=0.15,<0.16",
"mypy>=1.13",
"pytest>=9.0.3",
"pytest-asyncio>=0.24",
"pytest-cov>=6.0",
"pytest-xdist>=3.5",
"respx>=0.21", # Mock httpx
"dirty-equals>=0.8", # Pydantic test helpers
# Dev-time deps that some unit tests import directly. Living here
# rather than in ``[dependency-groups].dev`` so a plain
# ``pip install -e ".[dev]"`` (used by CI and contributors) covers
# the entire test surface.
# Capped at <1.4: starlette 1.2.0 (2026-05-28) makes its TestClient
# require ``httpx2`` instead of ``httpx``, so collection-time imports in
# ``tests/unit/test_a2a_protocol.py`` and ``test_server_app_full.py``
# raise ``StarletteDeprecationWarning`` → ``ModuleNotFoundError``. The
# ``httpx<1.0`` core pin already prevents installing ``httpx2``, so the
# only sane cap is starlette itself. Lift together with the httpx cap.
# Test-only: nothing under ``src/`` imports starlette, and the ``server``
# extra gets it transitively via fastapi.
"starlette<1.4",
"fastmcp>=3.2.0", # MCP server tests in tests/unit/test_fastmcp.py
"pyyaml>=6.0.3", # Playbook YAML loader tests
"redis>=7.1.0", # Redis checkpointer tests
"sqlalchemy>=2.0.46", # PostgreSQL checkpointer schema tests
"moto>=5.0", # mock S3 for tests/unit/test_memory_backends_s3.py
"cryptography>=42", # signed audit trail tests in tests/unit/test_signed_audit.py
"temporalio>=1.7", # durable run tests in tests/unit/test_temporal_durable.py
"dbos>=2.0", # durable run tests in tests/unit/test_dbos_durable.py
]
[project.scripts]
locus = "locus.cli:main"
locus-runner = "locus.runner.main:main"
[project.urls]
Homepage = "https://github.com/oracle/locus"
Documentation = "https://locusagents.ai/"
Repository = "https://github.com/oracle/locus"
Issues = "https://github.com/oracle/locus/issues"
# =============================================================================
# Hatch Configuration
# =============================================================================
[tool.hatch.build.targets.wheel]
packages = ["src/locus"]
# The Locus skill for Codex is the repository's ``.agents/skills/locus``; the
# wheel carries it as ``locus/codex/skill`` so ``locus codex init`` can place it.
[tool.hatch.build.targets.wheel.force-include]
".agents/skills/locus" = "locus/codex/skill"
[tool.hatch.build.targets.sdist]
include = [
"/.agents",
"/src",
"/tests",
"/README.md",
"/LICENSE",
"/LICENSE.txt",
"/NOTICE",
"/THIRD_PARTY_LICENSES.txt",
]
# Environments
[tool.hatch.envs.default]
# Pull in ``server`` (fastapi) and ``mcp`` so the strict mypy pass
# under ``hatch run check`` has the right type stubs available for
# ``locus.server.*``, ``locus.a2a.*``, and ``locus.integrations.*``.
# Without these, mypy reports "Untyped decorator makes function ..."
# on every FastAPI ``@app.get/.post`` and on MCP imports.
features = ["dev", "server", "mcp", "openai", "anthropic", "oci", "oci-sandbox", "oracledb", "rag", "s3", "mem0"]
# The security-domain tooling is its own distribution in this repo. Install it
# into the dev environment (editable) so one ``hatch run check`` lints, types
# and tests both.
[tool.hatch.envs.default.workspace]
members = ["packages/locus-sdk-security"]
[tool.hatch.envs.default.scripts]
# Default test target = unit tests only (deterministic, no external deps).
# Integration tests need credentials / live services and are run via
# `test-integration` or `test-all` when those are available.
test = "pytest {args:tests/unit packages/locus-sdk-security/tests/unit}"
# Check every Python snippet in README.md / examples/README.md against the
# installed SDK. Also runs as a unit test, so CI gates it either way; this
# is the fast local loop when editing docs.
snippets = "python scripts/check_doc_snippets.py {args}"
# The version is asserted in four documents no tool keeps in agreement.
# Run this before tagging — CI runs it too, in the lint job.
release-check = "python scripts/check_release_consistency.py {args}"
test-integration = "pytest {args:tests/integration packages/locus-sdk-security/tests/integration}"
test-all = "pytest {args:tests packages/locus-sdk-security/tests}"
test-cov = "pytest --cov=src/locus --cov=packages/locus-sdk-security/src/locus_security --cov-report=term-missing --cov-report=html --cov-report=json {args:tests/unit packages/locus-sdk-security/tests/unit}"
# Refresh the per-file coverage baseline (commit the resulting
# ``scripts/coverage_baseline.json``).
cov-baseline-update = [
"pytest --cov=src/locus --cov=packages/locus-sdk-security/src/locus_security --cov-report=json {args:tests/unit packages/locus-sdk-security/tests/unit}",
"python scripts/coverage_ratchet.py --update",
]
test-fast = "pytest -n auto {args:tests/unit packages/locus-sdk-security/tests/unit}"
lint = "ruff check src tests examples packages"
lint-fix = "ruff check --fix src tests examples packages"
format = "ruff format src tests examples packages"
format-check = "ruff format --check src tests examples packages"
typecheck = "mypy src/locus packages/locus-sdk-security/src/locus_security"
# CI gate: every check the project enforces, in one command. A clean run
# of `hatch run check` is what `_lint.yml` runs. The trailing ``test-cov``
# runs the deterministic unit suite under coverage and FAILS the whole
# command when unit line+branch coverage drops below ``fail_under`` (95%)
# in ``[tool.coverage.report]`` — so the org-wide ≥95% gate is enforced
# locally and in CI by a bare ``hatch run check``.
check = [
"format-check",
"lint",
"typecheck",
"test-cov",
]
all = [
"format",
"lint-fix",
"typecheck",
"test",
]
[tool.hatch.envs.docs]
features = ["docs", "openai"]
[tool.hatch.envs.docs.scripts]
serve = "mkdocs serve {args}"
build = "mkdocs build --strict {args}"
# Validates every ```python block on the site against the installed SDK.
snippets = "python scripts/check_snippets.py {args:docs}"
# The captured output on the notebook pages, re-run against this checkout.
outputs = "python scripts/notebook_output.py {args:--check}"
[tool.hatch.envs.test]
features = [
"dev",
"openai", "anthropic",
"telemetry", "mcp", "server",
"redis", "postgresql", "mysql", "opensearch", "s3",
"pgvector", "qdrant", "chroma", "cohere", "rerank-local", "mem0",
]
[[tool.hatch.envs.test.matrix]]
python = ["3.11", "3.12", "3.13", "3.14"]
# =============================================================================
# Ruff Configuration
# =============================================================================
[tool.ruff]
target-version = "py311"
line-length = 100
src = ["src", "tests", "packages/locus-sdk-security/src"]
[tool.ruff.lint]
select = [
"A", # flake8-builtins
"ARG", # flake8-unused-arguments
"ASYNC", # flake8-async
"B", # flake8-bugbear
"BLE", # flake8-blind-except
"C4", # flake8-comprehensions
"C90", # mccabe complexity
"DTZ", # flake8-datetimez
"E", # pycodestyle errors
"EM", # flake8-errmsg
"ERA", # eradicate (commented code)
"EXE", # flake8-executable
"F", # pyflakes
"FA", # flake8-future-annotations
"FBT", # flake8-boolean-trap
"FLY", # flynt (f-string conversion)
"FURB", # refurb
"G", # flake8-logging-format
"I", # isort
"ICN", # flake8-import-conventions
"INT", # flake8-gettext
"ISC", # flake8-implicit-str-concat
"LOG", # flake8-logging
"N", # pep8-naming
"PERF", # perflint
"PGH", # pygrep-hooks
"PIE", # flake8-pie
"PL", # pylint
"PT", # flake8-pytest-style
"PTH", # flake8-use-pathlib
"PYI", # flake8-pyi
"Q", # flake8-quotes
"RET", # flake8-return
"RSE", # flake8-raise
"RUF", # ruff-specific
"S", # flake8-bandit (security)
"SIM", # flake8-simplify
"SLF", # flake8-self
"SLOT", # flake8-slots
"T10", # flake8-debugger
"T20", # flake8-print
"TCH", # flake8-type-checking
"TID", # flake8-tidy-imports
"TRY", # tryceratops
"UP", # pyupgrade
"W", # pycodestyle warnings
"YTT", # flake8-2020
]
ignore = [
"E501", # line too long (handled by formatter)
"PLR0913", # too many arguments
"PLR2004", # magic value comparison
"PLR0915", # too many statements
"PLR0912", # too many branches
"PLR0911", # too many return statements
"S101", # assert usage (fine in tests)
"TRY003", # long exception messages
"TRY301", # raise within try
"EM101", # string literal in exception
"EM102", # f-string in exception
"FBT001", # boolean positional argument (common in SDK APIs)
"FBT002", # boolean default positional argument
"PLC0415", # import not at top level (needed for lazy imports)
"TC001", # move import into type-checking block (too verbose)
"TC002", # move import into type-checking block
"TC003", # move import into type-checking block
"ERA001", # commented out code (might be intentional docs)
# BLE001 (blind-except) is NOT ignored globally. Each `except Exception`
# must be annotated with `# noqa: BLE001 — reason`, explaining WHY
# catching broadly is correct at that site (e.g. "tool body can raise
# anything", "missing == absent by design"). New unannotated
# `except Exception` clauses will fail CI.
"RET504", # unnecessary assignment before return
"RET505", # unnecessary else after return
"RET506", # unnecessary else after raise
"SIM108", # use ternary operator
"ARG002", # unused method argument
"ARG001", # unused function argument
"PERF401", # use list comprehension (style preference)
"PERF403", # use dict comprehension
"B027", # empty method without abstract
"SIM102", # collapsible if
"SIM103", # return instead of if-else-bool
"SIM105", # use contextlib.suppress
"PLW0603", # global statement (needed for registry pattern)
"C901", # function too complex
"TRY300", # consider moving to else block
"PTH123", # use Path instead of open
"RUF022", # __all__ not sorted
"RUF012", # mutable class variable annotation
"RUF100", # unused noqa directive
"RUF005", # consider unpacking
"PYI034", # __aenter__ return type
"PYI036", # stub bad exit annotation
"PERF402", # use list.copy
# NOTE: S105 (hardcoded-password-string) is intentionally NOT suppressed
# project-wide. Use per-line `# noqa: S105` for known-false-positive
# literals (e.g. schema column names that happen to contain "password").
"S110", # try-except-pass
"S112", # try-except-continue
"UP041", # use PEP 585 instead of typing
# GSAR (locus.reasoning.gsar*) reproduces math notation from
# arXiv:2604.23366 — Greek letters (ρ, τ), set operators (∪, ⊔),
# and section markers (§) appear in docstrings and comments. The
# ambiguous-confusable lints would force us to translate them
# into ASCII, which loses the trace back to the paper.
"RUF001", # string literal contains ambiguous unicode character
"RUF002", # docstring contains ambiguous unicode character
"RUF003", # comment contains ambiguous unicode character
]
[tool.ruff.lint.per-file-ignores]
# The grounding benchmark is a `python -m locus.reasoning.grounding_eval` CLI —
# printing its report is the point.
"src/locus/reasoning/grounding_eval.py" = ["T201"]
# Same for the rogue challenge (`python -m locus.rogue`): it is a terminal
# demo whose whole output — banner, per-attempt verdict, scoreboard, audit
# dump — is printed. It ships inside the package so it runs from an install
# rather than a clone.
"src/locus/rogue/*.py" = ["T201"]
# The overhead benchmark is a CLI whose entire output is the report it
# prints, and it shells out to `sys.executable` to time a cold import — the
# only way to measure a cost that is paid once per process.
"benchmarks/*.py" = ["T201", "S603"]
"examples/checkpointer_examples.py" = ["ALL"]
"examples/config.py" = ["F821", "RET503"]
"packages/*/examples/config.py" = ["F821", "RET503"]
"examples/projects/**/*.py" = ["PLE2510"]
# ``**/`` so the workspace packages' tests/ and examples/ get the same rules.
"**/tests/**/*.py" = [
"ARG", # unused arguments in fixtures
"FBT", # boolean trap in tests
"PLR2004", # magic values
"S101", # assert
"SLF001", # private member access
"N802", # test method names with mixed case (test_implicit_AND_…)
"RUF059", # unpacked-but-unused vars in (state, events) destructuring
"B011", # assert False — idiomatic in try/except asserting raise
"PT015", # same as above — pytest's preferred form is opt-in
"F841", # pytest.importorskip("x") stored but unused — intentional
"F811", # multiple TestXxx classes with the same name across sections
"PLW0108", # lambdas passed to callbacks in on_event= patterns
"PT011", # pytest.raises too broad — fine in cross-model tests
"A001", # builtin shadowing — fixture param names
"DTZ005", # datetime.now without tz — fine for timing assertions
"T201", # print — diagnostic output in integration tests
"BLE001", # blind except — test fixtures swallow env-setup errors on purpose
"PTH110", # os.path.exists — idiomatic in conftest skip-gates
"PTH111", # os.path.expanduser — idiomatic for local config probing
"S108", # /tmp/... test strings — path construction, not real I/O
"S324", # md5 — non-security fingerprinting in test helpers
"S603", # subprocess — integration tests invoke example scripts intentionally
"E402", # imports after pytest.importorskip() — required pattern
"ASYNC221", # subprocess in async test — integration tests run example scripts
"ASYNC230", # async open() — fine in tests, simpler than aiofiles round-trip
"ASYNC240", # pathlib in async tests — fine, trio.Path/anyio.path overkill
]
"**/examples/**/*.py" = [
"S608", # some notebooks construct SQL from validated identifiers
"T201", # print — notebooks/examples intentionally use print
"DTZ005", # datetime.now without tz — fine for demo UX
"BLE001", # blind except — simplified error handling in examples
"S108", # /tmp/... demo paths — examples write to /tmp on purpose
"S311", # non-cryptographic random — demo data only
"S501", # httpx verify=False — only used in local-docker demo paths
"PTH110", # os.path.exists — keep examples accessible to readers
"PTH111", # os.path.expanduser — idiomatic for user-facing configs
"PTH118", # os.path.join — pedagogical readability over Path / "x"
"PTH208", # os.listdir — same; notebooks avoid pathlib import noise
"A002", # builtin arg shadowing — pedagogical clarity
"B007", # unused loop variable — counter patterns are pedagogical
"SLF001", # private-member access — examples illustrate internals
"ARG005", # unused lambda arg — demo lambda signatures
"ASYNC210", # blocking HTTP — notebooks may demo sync libs alongside async
"ASYNC240", # os.path in async — examples favor stdlib readability
]
"scripts/**/*.py" = [
"T201", # print — runner scripts are CLI tools that print to stdout
"S603", # subprocess call — runner intentionally execs notebooks
"BLE001", # blind except — runner reports failures generically
]
"src/locus/cli.py" = [
"T201", # print — the locus command's output is its interface
]
"src/locus/memory/backends/**/*.py" = [
"S608", # SQL built from validated identifiers (regex-checked at init)
"PYI063", # PEP 570 positional-only — `__context` is Pydantic's spelling
]
"src/locus/rag/stores/**/*.py" = [
"S608", # SQL built from validated identifiers
"PYI063", # PEP 570 positional-only — `__context` is Pydantic's spelling
]
"src/locus/memory/store_backends/**/*.py" = [
"S608", # SQL built from validated identifiers (regex-checked at init)
"PYI063", # PEP 570 positional-only — `__context` is Pydantic's spelling
]
"tests/integration/test_pg_memory.py" = [
"S608", # SQL built from a constant test table name, not user input
]
"src/locus/agent/agent.py" = [
"SLF001", # Private attr access for internal state (_has_unverified_writes)
]
"src/locus/agent/initializer.py" = [
"SLF001", # Extracted constructor body — its job is to populate
# ``Agent._*`` private attrs in place.
]
"src/locus/integrations/fastmcp.py" = [
# S102 (exec) was formerly required for the MCP tool wrapper; the
# closure-based implementation in _create_tool_wrapper no longer uses
# exec, so the rule stays enforced.
"SLF001", # Private member access for agent integration
]
"src/locus/rag/**/*.py" = [
"ASYNC230", # Blocking I/O in async — some client SDKs are sync, wrapped in executor
"ASYNC240", # Path methods in async — local file reads are fast/local
"F841", # Unused vars in multimodal processing pipelines
"FURB110", # Ternary vs or — readability preference
"PTH108", # os.unlink vs Path.unlink — temp file cleanup
"PTH111", # os.path.expanduser — local config paths
"S608", # SQL built from validated identifiers (pgvector store)
"UP042", # str+Enum — needed for serialization compat
]
"src/locus/core/interrupt.py" = [
"N818", # Exception names without Error suffix — public API, can't rename
]
"src/locus/hooks/provider.py" = [
"FBT003", # Boolean positional in _init() — internal event construction
]
"src/locus/hooks/plugin.py" = [
"SLF001", # Private attr _is_hook for decorator marker
]
"src/locus/skills/plugin.py" = [
"SLF001", # Closure accessing parent plugin's private members
]
"src/locus/multiagent/graph.py" = [
"PYI063", # PEP 570 positional-only — compat with older Python
"PGH003", # Blanket type: ignore — pydantic internal typing
]
"src/locus/memory/backends/postgresql.py" = [
"S608", # SQL from validated identifiers
"PYI063", # PEP 570 positional-only
]
# Docs-site tooling (mkdocs hooks + their tests), ported from the former docs
# repository with its own, lighter lint profile.
"hooks/*.py" = [
"B023", # closures over the loop variable are consumed inside the same iteration
]
"tests/docs/*.py" = [
"PT018", # compound asserts read better in these table-driven checks
"RUF043", # literal regex patterns in pytest.raises(match=...)
]
"src/locus/_oracle_pool_cache.py" = [
"SLF001", # this helper IS the private-state plumbing — by design
]
"src/locus/memory/backends/oci_bucket.py" = [
"FURB162", # Timezone replacement — OCI API returns specific format
]
[tool.ruff.lint.isort]
known-first-party = ["locus", "locus_security"]
force-single-line = false
lines-after-imports = 2
[tool.ruff.lint.flake8-tidy-imports]
ban-relative-imports = "all"
[tool.ruff.lint.flake8-type-checking]
strict = true
[tool.ruff.lint.mccabe]
max-complexity = 12
[tool.ruff.lint.pylint]
max-args = 8
[tool.ruff.format]
quote-style = "double"
indent-style = "space"
skip-magic-trailing-comma = false
line-ending = "auto"
docstring-code-format = true
docstring-code-line-length = 80
# =============================================================================
# MyPy Configuration
# =============================================================================
[tool.mypy]
python_version = "3.11"
# Strict mode across the modules that aren't in the ignore_errors
# override block below. The override list is deliberately shrinking
# over time — see DEPRECATION.md.
disallow_untyped_defs = true
disallow_incomplete_defs = true
disallow_untyped_decorators = true
warn_return_any = true
warn_unused_ignores = true
warn_redundant_casts = true
warn_unused_configs = true
check_untyped_defs = true
no_implicit_optional = true
show_error_codes = true
show_column_numbers = true
pretty = true
# Pydantic plugin
plugins = ["pydantic.mypy"]
[[tool.mypy.overrides]]
module = [
"openai.*",
"oci.*",
"oracledb.*",
"oracleagentmemory.*",
"google.*",
"requests.*",
"portkey_ai.*",
"mcp.*",
"anthropic.*",
"opensearchpy.*",
"redis.*",
"asyncpg.*",
"mysql.*",
"fastapi.*",
"uvicorn.*",
"whisper.*",
# Optional integration deps (lazy-imported).
"boto3.*",
"botocore.*",
"qdrant_client.*",
"chromadb.*",
"cohere.*",
"sentence_transformers.*",
"mem0.*",
"moto.*",
# Optional deps for ``locus.rag.multimodal`` — only loaded inside
# try/except for users who want OCR / PDF / image / audio extraction.
"pytesseract.*",
"PIL.*",
"pypdf.*",
"PyPDF2.*",
"pdf2image.*",
# Browser tools (``locus.tools.browser``) import Playwright lazily.
"playwright.*",
"google.genai",
"google.genai.*",
# The coding harness's OpenShell backend imports it lazily.
"openshell",
"openshell.*",
]
ignore_missing_imports = true
# numpy ships type stubs, but some numpy/Python combinations make mypy abort
# with a stub parse error ("errors prevented further checking" in
# numpy/__init__.pyi) — seen on the 3.13/3.14 CI runners. numpy is only a
# transitive dependency (chromadb / torch / scikit-learn), never part of
# Locus's typed surface, so skip analyzing it entirely.
[[tool.mypy.overrides]]
module = ["numpy", "numpy.*"]
follow_imports = "skip"
follow_imports_for_stubs = true
ignore_missing_imports = true
# Modules with dynamic types from external SDKs — relaxed checking.
#
# Migration in progress (issue #34): each module has been audited and either
# (a) lifted out of this list and back into strict checking, with per-line
# ``# type: ignore[code]`` for genuine SDK-stub gaps, or (b) retained here
# until its module-level migration PR lands. The lifted set is enforced by
# the strict block above.
# All ``locus.*`` modules are now type-checked under the strict block
# above. The ``[[tool.mypy.overrides]]`` ``ignore_errors`` block has been
# removed — see #34 for the migration history.
[tool.pydantic-mypy]
init_forbid_extra = true
init_typed = true
warn_required_dynamic_aliases = true
# =============================================================================
# Pytest Configuration
# =============================================================================
[tool.pytest.ini_options]
minversion = "8.0"
testpaths = ["tests", "packages/locus-sdk-security/tests"]
pythonpath = ["src", "packages/locus-sdk-security/src"]
asyncio_mode = "auto"
asyncio_default_fixture_loop_scope = "function"
addopts = [
"-ra",
"-q",
"--strict-markers",
"--strict-config",
]
markers = [
"slow: marks tests as slow",
"integration: marks tests as integration tests",
]
filterwarnings = [
"error",
"ignore::DeprecationWarning",
"ignore::ResourceWarning",
"ignore::pytest.PytestUnraisableExceptionWarning",
]
# =============================================================================
# Coverage Configuration
# =============================================================================
[tool.coverage.run]
source = ["src/locus", "packages/locus-sdk-security/src/locus_security"]
branch = true
parallel = true
omit = [
"src/locus/__about__.py",
# External integrations requiring database/API connections
"src/locus/rag/stores/opensearch.py",
"src/locus/rag/stores/pgvector.py",
"src/locus/rag/embeddings/openai.py",
"src/locus/memory/backends/opensearch.py",
"src/locus/memory/backends/oci_bucket.py",
# PgMemory needs a live Postgres+pgvector; covered by integration tests.
"src/locus/memory/store_backends/postgresql.py",
# PgCheckpointer likewise: its unit tests pin the statements it sends, and
# tests/integration/test_pg_checkpointer.py covers it on a real Postgres.
"src/locus/memory/backends/pg_checkpointer.py",
]
[tool.coverage.report]
exclude_lines = [
"pragma: no cover",
"def __repr__",
"raise AssertionError",
"raise NotImplementedError",
"if __name__ == .__main__.:",
"if TYPE_CHECKING:",
"if typing.TYPE_CHECKING:",
"@abstractmethod",
"@abc.abstractmethod",
"class .*\\bProtocol\\):",
"@overload",
]
fail_under = 95
show_missing = true
# Note: ``[dependency-groups]`` (PEP 735) was previously used here but
# ``pip install -e ".[dev]"`` doesn't pick those up — the same deps are
# now in the ``[project.optional-dependencies].dev`` extra above. Kept
# this comment as a marker so a future ``hatch sync`` doesn't reintroduce
# the duplicate.
# =============================================================================
# Workspace
# =============================================================================
# The security-domain tooling ships as its own distribution from this repo
# (``packages/locus-sdk-security/``). It depends on locus-sdk; resolve
# both sides locally.
[tool.uv]
# Security overrides for transitive deps whose upstreams pin them below the
# patched version: the `oci` SDK caps `cryptography` < 48 and
# `oracleagentmemory` (alpha, pinned < 27) caps `litellm`/`aiohttp`. Force the
# patched versions to clear the advisories; drop each entry once its upstream
# relaxes the cap. NOTE: oci uses cryptography for request signing — unit tests
# mock OCI, so live signing against cryptography 48+ is verified by the
# integration suite, not CI.
override-dependencies = [
"litellm>=1.84.0",
"cryptography>=48.0.1",
"aiohttp>=3.14.1",
# litellm 1.106.x caps filelock < 4.0 (its main branch already allows < 5);
# only litellm's proxy CLI imports filelock, and the 4.0 on-disk protocol
# change is confined to SoftReadWriteLock, which nothing here uses.
"filelock>=4.2.4",
# oracleagentmemory caps pillow < 13; the oci SDK caps urllib3 < 2.8 but
# vendors its own copy, so the top-level pin only serves the advisories.
"pillow>=13.0.0",
"urllib3>=2.8.0",
]
[tool.uv.workspace]
members = ["packages/locus-sdk-security"]
[tool.uv.sources]
locus-sdk-security = { workspace = true }