Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .github/workflows/verify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,24 @@ jobs:
--root "$RUNNER_TEMP/borg-source" \
--allowlist "$RUNNER_TEMP/borg-source/RELEASE-ALLOWLIST.json" \
--inventory "$RUNNER_TEMP/borg-source/RELEASE-INVENTORY.json"
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
with:
python-version: '3.12'
- name: Install hash-pinned connector test dependencies
shell: bash
run: |
python3 -m venv "$RUNNER_TEMP/borg-connector-tests"
"$RUNNER_TEMP/borg-connector-tests/bin/python" -m pip install \
--require-hashes -r connector/requirements-test.lock
- name: Test connector trust boundaries and compatibility
working-directory: connector
shell: bash
run: |
"$RUNNER_TEMP/borg-connector-tests/bin/python" -B -m unittest -v \
test_recall_policy test_operation_diagnostics test_job_portability test_fleet \
test_connector.ConnectorTests test_capabilities test_concurrency \
test_resilience.ReceiptTests test_resilience.MiddlewareReceiptTests \
test_resilience.LaneTests
- name: Verify release, adapter and installer guards
run: python3 -B -m unittest installer.test_release_guard installer.test_adapters installer.test_startup_preflight
- name: Install pinned native Codex runtime
Expand Down
77 changes: 56 additions & 21 deletions RELEASE-INVENTORY.json
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
{
"excluded_self": "RELEASE-INVENTORY.json",
"file_count": 372,
"file_count": 379,
"files": [
{
"bytes": 1914,
"path": ".github/workflows/site.yml",
"sha256": "6bfdccbe0a3994dba7c402c2f7a2e0713de367e92476c68f680bc87439e7d1cf"
},
{
"bytes": 1756,
"bytes": 2686,
"path": ".github/workflows/verify.yml",
"sha256": "d97bb88a2b4944ebdaa16b70322c11f1ac568c35d9321cdb9ed133fa4573e38b"
"sha256": "155639c36e00251bd6979c6a86d969078e44bc65bbb3be6001b36c51a9c43535"
},
{
"bytes": 29,
Expand Down Expand Up @@ -273,9 +273,9 @@
"sha256": "1600e1b9bb2eda4fad5790d5ef17bb17d2e868439b58fceebcad09e1af3c071d"
},
{
"bytes": 5983,
"bytes": 9208,
"path": "connector/README.md",
"sha256": "16cc2db889c1afa8a5c9a9b3946bd9c814f44bb6c90612479e65b7017b8c2581"
"sha256": "c6bb52f9a9ff4d28589266034140fe56a41570b679f50ec773f8569c07fa16ec"
},
{
"bytes": 791,
Expand All @@ -288,9 +288,9 @@
"sha256": "5dbd10ab2ca3ac830d79b2fc16455174496cc0d04e20e4911eeddff8073aa087"
},
{
"bytes": 30579,
"bytes": 31144,
"path": "connector/borg_context_server.py",
"sha256": "1da7e9bab3f2dfcb73de8ce2f990c426142b5491c47078bbaf2335ab007655c2"
"sha256": "3133dd39aea9f15575ea8588ef04577d5cbd8e3a5e21969101bcbd14536cc58f"
},
{
"bytes": 7861,
Expand All @@ -308,9 +308,9 @@
"sha256": "c2d180488c05184b44efeda60255142a605ebe9e1287de8f83d0ad46c4fb0b7a"
},
{
"bytes": 39887,
"bytes": 41715,
"path": "connector/computer_tools.py",
"sha256": "1a9d13e51839f275f8ffb45b012b878ba114474da9386542146eb8175927bc4c"
"sha256": "14cc0e44b8fbde5a60b5ec9a6fa766daba7217ded66001d9f444cc45e3a52c0e"
},
{
"bytes": 9415,
Expand All @@ -333,14 +333,14 @@
"sha256": "b47e976b390b5867fce5b88f5e23367d934cc6271c1b64f36087a19311ea79f4"
},
{
"bytes": 13799,
"bytes": 14901,
"path": "connector/fleet_tools.py",
"sha256": "930e947ad63c7439f5fdd0cf8d90bd6d73445e35303e6fb0b06800a133f70d28"
"sha256": "c900c72a65a08367aa01e07d4b7fda08529b7384ba4b933151dcf65ce42c44f6"
},
{
"bytes": 16169,
"bytes": 16583,
"path": "connector/job_tools.py",
"sha256": "862d0c1398d88cf0219c33c1650a006d8309c16daff984d0de2bc3320ab4e137"
"sha256": "7c6995becd1557ef39a0746bf8667133dab33a2e4c2dc89f3195ea75a1066642"
},
{
"bytes": 18999,
Expand All @@ -353,9 +353,14 @@
"sha256": "c7d89ef33773211c7f01921a6c158b89edd7235b86e82e648898bc2170fd4d1d"
},
{
"bytes": 6792,
"bytes": 3290,
"path": "connector/operation_diagnostics.py",
"sha256": "c97aebf123811cb14f6c139ab780db8a0fd16dbb95dedb712a93f9b0e999ef2c"
},
{
"bytes": 7484,
"path": "connector/operation_receipts.py",
"sha256": "49aed0c2e7aab70017a2b8a5d2ebfbd88b89ff6df286a55e77519f89be0cb495"
"sha256": "7f6e05ce4f125a72f3e5edacfc13a81e538d7dddeac557d112546f7504afd012"
},
{
"bytes": 7463,
Expand All @@ -367,6 +372,11 @@
"path": "connector/process_resources.py",
"sha256": "fc5589a3692aa250ef6d6690746579f009f98b62ac040b546829b2b85f67e58d"
},
{
"bytes": 4756,
"path": "connector/recall_policy.py",
"sha256": "93b93096adc1b34ec8992c8a2e28f43976a81a51b68f15d3fba7d4b13b3563da"
},
{
"bytes": 7218,
"path": "connector/remote_tools.py",
Expand All @@ -377,6 +387,16 @@
"path": "connector/repository_inspection.py",
"sha256": "ba46577ba89fbc0e1fd9c1964a777474d6d48e3af3b0135e5ab126dbb3b27068"
},
{
"bytes": 122,
"path": "connector/requirements-test.in",
"sha256": "01bf67ce81dae17a77b7bffd0ac5ea88b6ec9c2956d862ee2db607ac669d2566"
},
{
"bytes": 79264,
"path": "connector/requirements-test.lock",
"sha256": "fbddfa7ef0926ac429ec127444a71ff4b72485459b5930f86465ceec1c56581b"
},
{
"bytes": 1008,
"path": "connector/runtime_paths.py",
Expand Down Expand Up @@ -423,9 +443,9 @@
"sha256": "0894b284dd34af6495190116a4b7670bede640225b81155278061eabccedaf7b"
},
{
"bytes": 15695,
"bytes": 16225,
"path": "connector/test_connector.py",
"sha256": "f88be7ca2e80c2072c8b968551438c054e1d940b464fc5296e45c4ffa94fa82f"
"sha256": "ad52ce4b14301a57bde1f248011fd04388c1f9108ca589078d978613e0089674"
},
{
"bytes": 5688,
Expand Down Expand Up @@ -457,6 +477,16 @@
"path": "connector/test_independent_settings.py",
"sha256": "9dfe7b26bec7360fe0305dc9708e7ada6ce3109964a040f05e3a914d9be73948"
},
{
"bytes": 2828,
"path": "connector/test_job_portability.py",
"sha256": "b3f8a1e0d83367329319ee3e2b0dde4b32fbdab2bca8580dc56d17d681f1aca8"
},
{
"bytes": 15122,
"path": "connector/test_operation_diagnostics.py",
"sha256": "89d60593f362e984ed4f3e7b9610c55e94a73c7c81b7152a6093f2d1d11750b7"
},
{
"bytes": 8806,
"path": "connector/test_process_handoff.py",
Expand All @@ -472,15 +502,20 @@
"path": "connector/test_process_resources.py",
"sha256": "ef9908cf64b3729c7ea3653f50ac19ba555567c9997a5ac1bdb01803a6739283"
},
{
"bytes": 9640,
"path": "connector/test_recall_policy.py",
"sha256": "5c8cdc792a06b08c6e888004f8bc07d472ace2ba32b330d8395354dda3dd9b3f"
},
{
"bytes": 8418,
"path": "connector/test_repository_inspection.py",
"sha256": "af93c743bc29f59ab44798acebd6e6429b730a48e2e958fa8757af395e2f5406"
},
{
"bytes": 14511,
"bytes": 14542,
"path": "connector/test_resilience.py",
"sha256": "865b2f3bc8b45ac43cfc322c49a45835965240f9c7903a1c6996e98324894af4"
"sha256": "41e95dc345e125b839b186bf8f59d2fd5a0ea642195284a54913b9eaf9240ff1"
},
{
"bytes": 23,
Expand Down Expand Up @@ -1863,7 +1898,7 @@
"sha256": "cd4bdb4529012e0cfcd38e059215f9ea433b8ee1fa636276b36c6515e7949e28"
}
],
"inventory_sha256": "787241ae9bf10d37224ad69423f5b37cbc3d8110df4c4e7d9461320adf1afbe6",
"inventory_sha256": "1233b085a870bd6a7e9ada3a9df14bb73f9cb20ecb70f4769cf0b876a331c405",
"schema": "borg-public-inventory/v1",
"total_bytes": 87734443
"total_bytes": 87858782
}
51 changes: 51 additions & 0 deletions connector/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,54 @@ a bounded migration mechanism, not indefinite process-history storage.
Configuration and service commands are described in [INSTALL.md](../docs/INSTALL.md).
For an owner's Cloudflare/ChatGPT connection, follow [WEB.md](../docs/WEB.md). Optional
SSH and credential registries start empty and must be configured for that owner.

## Recall result selection

`borg_search` and `borg_project_context` share a read-only selection policy. The
existing response fields and call arguments are retained. Both now report
`result_floor` (`populated`, `sparse`, or, for degraded project context,
`unavailable`) and a versioned `retrieval` object with candidate/returned counts,
omission reasons, and the policy used. An upstream outage is not a successful
empty search. All returned memories remain **candidate context**, not authority.

Semantic queries preserve upstream ordering and valid low-scoring candidates:
a similarity score is not a confidence probability. Double-quoted names or
phrases require literal, whitespace-normalized matches. Opaque identifiers require
matching record text or the exact native row ID. For example, `"Ada Example" role`
does not fill the result with other people sharing only one name. Unquoted names
remain semantic searches; this is not a replacement for entity resolution.
Literal matches do not prove a claim is true, current, or about a unique entity.

Only a bounded upstream pool is inspected (at most 25 candidates). A sparse
response therefore means no additional eligible candidates in that pool, not
proof that the entire corpus lacks the requested fact. A small, whole-record
vacuity rule withholds tautological tool statements; discussion of those strings
is retained. Nothing is deleted, quarantined in storage, or promoted to a higher
trust tier. The policy performs no additional model calls or memory writes.

## Durable effect and failure diagnostics

New operation receipts use version 2 and retain existing fields. Their bounded
`diagnostics` object records execution phase, effect state, typed cause, and,
for fleet calls, target host/tool, verified instance/generation and target receipt
when available. Credential values, home paths, arguments, error text and result
payloads are not stored in this object. Existing version-1 receipts remain readable.
`borg_operation_status` returns the same durable evidence after the request ends.

A post-dispatch error, including a permission or rate-limit error, is
`outcome_unknown` unless absence of execution is established by local preflight.
A timeout before dispatch is `not_started`; a lost response after dispatch is not.
`retryable` is true only for a known transient failure with `not_started` effects;
it is advice to recheck the target and admission, never an automatic retry.
`retry_without_change` remains false. Running receipts are provisional, and
interrupted receipts recover to unknown rather than authorizing a repeat.
Successful receipts prove completion of the native tool call, not deployment or
an external business outcome. Finalized receipts cannot regress after cancellation.

The connector trust and compatibility suites run on both CI operating systems
using `requirements-test.lock`, hash-pinned against the accepted runtime versions.

Durable jobs preserve `/bin/zsh -lc` where that executable exists. On POSIX
hosts without executable zsh they use `/bin/sh -c`. If neither shell is
available, launch is refused before creating job artifacts. The fallback and
its durable stdout/exit-status behavior are covered by cross-platform CI.
25 changes: 18 additions & 7 deletions connector/borg_context_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ async def mem0_call(client: Client, tool: str, args: dict[str, Any]) -> Any:


from repository_inspection import _repos_matching, _repo_snapshot, _needles
from recall_policy import select_memories, unavailable_retrieval


def _memory_summary(rows: Any) -> list[dict[str, Any]]:
Expand Down Expand Up @@ -360,7 +361,7 @@ async def borg_status(self) -> dict[str, Any]:
"ui_tools_configured": bool(ui),
"credential_handles_configured": bool(credentials),
"inbox_controller_configured": bool(computer.get("inbox_client_config")),
"failure_diagnostics_version": 2,
"failure_diagnostics_version": 3,
"readiness_notice": "Connector liveness, dependency readiness, freshness and completed-operation evidence are separate signals."},
"caller_boundary": "Dedicated local Bearer; the public gateway requires BORG owner OAuth independently of the ChatGPT account.",
"graph": graph, "source_notice": NOTICE, "activity": _activity("", 0),
Expand All @@ -380,13 +381,19 @@ async def borg_capabilities(self) -> dict[str, Any]:
return build_manifest(self.settings, schemas)

async def borg_search(self, query: str, limit: int = 6, include_graph: bool = True) -> dict[str, Any]:
"""Search across the owner's entire BORG memory collection. Results are candidate context."""
"""Search owner memory as candidates, not proof. Quote names/phrases for literal matching.
Opaque identifiers also require an exact match. Sparse describes the candidate pool,
not corpus-wide absence; upstream failures remain errors.
"""
if not query.strip() or len(query) > 2000:
raise ToolError("Supply a nonempty query of at most 2000 characters")
requested = max(1, min(int(limit), 12))
async with self.upstream() as (client, _):
rows = await mem0_call(client, "memory_search", {
"query": query, "limit": max(1, min(int(limit), 12)), "include_graph": include_graph})
return {"query": query, "results": _memory_summary(rows), "source_notice": NOTICE}
"query": query, "limit": min(requested * 4, 25), "include_graph": include_graph})
selected, selection = select_memories(query, rows, requested)
return {"query": query, "results": _memory_summary(selected), "source_notice": NOTICE,
**selection}

async def borg_projects(self, query: str = "", limit: int = 12) -> dict[str, Any]:
"""Find repositories locally; tolerate outage but fail closed on identity mismatch."""
Expand All @@ -409,12 +416,16 @@ async def borg_project_context(self, project: str, task: str = "", limit: int =
authorize()
memories = []
memory_status = "UNAVAILABLE"
selection = unavailable_retrieval()
query = " ".join((project, task)).strip()
requested = max(1, min(int(limit), 12))
try:
async with self.upstream() as (client, _):
rows = await mem0_call(client, "memory_search", {
"query": " ".join((project, task)).strip(), "limit": max(1, min(int(limit), 12)),
"query": query, "limit": min(requested * 4, 25),
"include_graph": True})
memories = _memory_summary(rows)
selected, selection = select_memories(query, rows, requested)
memories = _memory_summary(selected)
memory_status = "PASS"
except ToolError as exc:
if "identity or scope differs" in str(exc):
Expand All @@ -424,7 +435,7 @@ async def borg_project_context(self, project: str, task: str = "", limit: int =
repos = await asyncio.to_thread(_repos_matching, project, self.settings.project_roots, 6)
return {"project": project, "task": task, "observed_at": _now(), "memory_notice": NOTICE,
"memory_status": memory_status, "recalled_context": memories, "live_repositories": repos,
"activity": _activity(project, max(1, min(int(limit), 12)))}
"activity": _activity(project, requested), **selection}

async def borg_remember(self, text: str, scope: str | None = None) -> dict[str, Any]:
"""Save one owner-requested durable fact in BORG. Never store secrets, client personal data or dollar amounts.
Expand Down
Loading