Skip to content
Merged
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
19 changes: 19 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,25 @@ jobs:
BRAID_EVAL_API_KEY: ${{ secrets.BRAID_EVAL_API_KEY }}
BRAID_EVAL_BASE_URL: ${{ vars.BRAID_EVAL_BASE_URL }}
BRAID_EVAL_MODEL: ${{ vars.BRAID_EVAL_MODEL }}
BRAID_TANGLE_API_KEY: ${{ secrets.BRAID_TANGLE_API_KEY }}
BRAID_TANGLE_ENDPOINT: ${{ vars.BRAID_TANGLE_ENDPOINT }}
BRAID_TANGLE_MODEL: ${{ vars.BRAID_TANGLE_MODEL }}
BRAID_TANGLE_PROVIDER: ${{ vars.BRAID_TANGLE_PROVIDER }}
BRAID_TANGLE_RUNNER: ${{ vars.BRAID_TANGLE_RUNNER }}
BRAID_TANGLE_SANDBOX_API_KEY: ${{ secrets.BRAID_TANGLE_SANDBOX_API_KEY }}
BRAID_TANGLE_SANDBOX_ENDPOINT: ${{ vars.BRAID_TANGLE_SANDBOX_ENDPOINT }}
BRAID_TANGLE_SANDBOX_MODEL: ${{ vars.BRAID_TANGLE_SANDBOX_MODEL }}
BRAID_TANGLE_SANDBOX_PROVIDER: ${{ vars.BRAID_TANGLE_SANDBOX_PROVIDER }}
BRAID_TANGLE_SANDBOX_RUNNER: ${{ vars.BRAID_TANGLE_SANDBOX_RUNNER }}
BRAID_ANALYSIS_API_KEY: ${{ secrets.BRAID_ANALYSIS_API_KEY }}
BRAID_ANALYSIS_ENDPOINT: ${{ vars.BRAID_ANALYSIS_ENDPOINT }}
BRAID_ANALYSIS_MODEL: ${{ vars.BRAID_ANALYSIS_MODEL }}
BRAID_ANALYSIS_PROVIDER: ${{ vars.BRAID_ANALYSIS_PROVIDER }}
BRAID_ANALYSIS_RUNNER: ${{ vars.BRAID_ANALYSIS_RUNNER }}
BRAID_SUPERVISOR_ID: ${{ vars.BRAID_SUPERVISOR_ID }}
BRAID_SUPERVISOR_MESSAGE: ${{ vars.BRAID_SUPERVISOR_MESSAGE }}
BRAID_SUPERVISOR_ROOT: ${{ vars.BRAID_SUPERVISOR_ROOT }}
BRAID_SUPERVISOR_WORKER: ${{ vars.BRAID_SUPERVISOR_WORKER }}
BRAID_RELEASE_CHECKOUT: ${{ github.workspace }}
run: >-
pnpm release:collect --
Expand Down
18 changes: 14 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,23 @@ Braid owns the conversation, transcript, branches, approvals, activity, graph, a

The selected runner owns its native process and session, while `agent-runtime` owns admission, lifecycle, normalized events, and runtime control.

![Braid routing a Product engineer AgentProfile through agent-runtime to Pi and GLM-5.2 over Local CLI Bridge, then answering a cited trace-analysis question](artifacts/demo/braid-live-pi.gif)
![Braid using a Product engineer AgentProfile to fix code and run tests through agent-runtime, Local CLI Bridge, OpenCode, and GLM-5.2](artifacts/demo/braid-live-cli-bridge.gif)

This recording uses the packed Braid source, Pi 0.83.0, GLM-5.2, and a local CLI Bridge.
This recording shows a Braid terminal session captured from the working tree.

Pi changes a real workspace and passes its tests before `/ask` analyzes the frozen run and returns cited findings.
It opens the `Product engineer` AgentProfile, routes through `agent-runtime` and Local CLI Bridge, and runs OpenCode with GLM-5.2.

The [capture manifest](artifacts/demo/braid-live-pi.json) records the exact route, profile, limits, usage, cost, latency, workspace checks, and artifact hashes.
The user asks Braid to fix a failing JavaScript test.

The session shows the source edit, test command, one passing test, and final response `Tests pass through Braid via CLI Bridge.`

The [capture summary](artifacts/demo/braid-live-cli-bridge.json) records the displayed route, AgentProfile, final workspace state, test output, usage, latency, and image hashes.

The terminal records 82,613 input tokens, 203 output tokens, one model call, and 27,710 ms of model latency.

The [Pi and `/ask` recording](artifacts/demo/braid-live-pi.gif) shows a second real route followed by cited trace analysis.

Its [capture manifest](artifacts/demo/braid-live-pi.json) records the exact route, profile, limits, usage, cost, latency, workspace checks, and artifact hashes.

## Product path

Expand Down
Binary file added artifacts/demo/braid-live-cli-bridge.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
63 changes: 63 additions & 0 deletions artifacts/demo/braid-live-cli-bridge.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
{
"schemaVersion": 2,
"status": "passed",
"capturedAt": "2026-08-11T07:48:21.314Z",
"source": {
"packageVersion": "0.1.0",
"execution": "working-tree-braid-terminal",
"scope": "capture-summary-not-source-attestation"
},
"route": {
"connection": "Local CLI Bridge",
"endpoint": "http://127.0.0.1:3347",
"runtime": "agent-runtime",
"runtimeVersion": "0.131.7",
"runner": "opencode",
"runnerVersion": "1.17.18",
"provider": "zai-coding-plan",
"model": "glm-5.2",
"materializedModel": "opencode/zai-coding-plan/glm-5.2"
},
"profile": {
"name": "Product engineer",
"reasoningEffort": "high",
"maxOutputTokens": 4096
},
"task": {
"prompt": "Fix src/greeting.js so npm test passes. Run the test. When it is green, reply with these words joined into one sentence: Tests | pass | through | Braid | via | CLI | Bridge.",
"answer": "Tests pass through Braid via CLI Bridge.",
"runId": "run-a52d5488-bf6d-41ae-a2f5-380cec7f4949",
"turnId": "turn-2cb3f327-6a3f-4aa0-95c9-b0e6956f5ccf",
"operationId": "op-e71fed48-b595-429d-9a5e-202b4e1fae83",
"providerSessionId": "session-braid-run-a52d5488-bf6d-41ae-a2f5-380cec7f4949",
"environmentId": "environment-404036676011767ee530826620a6465e",
"status": "completed",
"inputTokens": 82613,
"outputTokens": 203,
"tokenStatus": "complete",
"promptCache": {
"freshInput": 16885,
"readInput": 65728,
"writeInput": 0
},
"llmCalls": 1,
"llmLatencyMs": 27710,
"materializationDigest": "6b5f951cce52eb9633a6999650f4d85ddebe3f9176d7226500912af140a53a8f",
"workspaceProof": {
"source": "export function greeting(name) {\n return `Hello, ${name}!`\n}\n",
"sourceSha256": "b2eeed0e781bd1ba796830399433b8000e687c06cc217a051d79f24ffb8318e7",
"testOutput": "> test\n> node --test\n\n✔ greets a user (0.460837ms)\nℹ tests 1\nℹ suites 0\nℹ pass 1\nℹ fail 0\nℹ cancelled 0\nℹ skipped 0\nℹ todo 0\nℹ duration_ms 40.369586",
"testOutputSha256": "5c98a415cb3f623534438a03b219f998a53ee793fdf74f7c63f04ea78d7ae5dd"
}
},
"terminal": {
"command": "braid --profile \"Product engineer\"",
"columns": 120,
"rows": 34,
"durationMs": 32346
},
"artifacts": {
"braid-live-cli-bridge.gif": "caabc8062b3947feecc351def8506f91eef9243b58a42df40c25d7568628ae3b",
"braid-live-cli-bridge.png": "19bbd5ab529d385f7e3c4d3f0bc6a2c7260383b59d49f685252755459e2dd603"
}
}
Binary file added artifacts/demo/braid-live-cli-bridge.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions docs/04-runtime-contracts.md
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,10 @@ Tangle sandbox records provider sandbox identity, lifecycle, cleanup, continuity

The current Tangle sandbox path is deleted after each turn and rejects session continuation.

If that ephemeral path requests approval, an answer, or a plan decision, Braid fails the turn with an unsupported-interaction explanation.

Braid does not display a resumable interaction after its environment is deleted.

The platform supports retained sandboxes, but Runtime does not yet expose safe retained identity and recovery to Braid.

The observation record never contains API keys, bearer tokens, SSH credentials, secret values, credential-bearing URLs, Docker host strings, or internal listener addresses.
Expand Down
18 changes: 18 additions & 0 deletions docs/08-verification.md
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,12 @@ The live Tangle sandbox check records sandbox account totals separately from per

It marks physical machine IP, effective allocation, and per-sandbox CPU, RAM, and storage cost unavailable unless the live provider reports them.

A sandbox `error` or failed terminal event must keep the Braid run failed while preserving measured usage and confirmed deletion.

A Runtime abort must reach any pending sandbox create request and settle the Braid run as aborted.

These checks guard [agent-runtime issue 781](https://github.com/tangle-network/agent-runtime/issues/781) and [agent-runtime issue 782](https://github.com/tangle-network/agent-runtime/issues/782).

## Reliability and recovery matrix

| Failure point | Required result |
Expand Down Expand Up @@ -530,6 +536,12 @@ After npm publication, the same clean-install, plain-flow, encrypted-storage, di

The final process validates those six records, adds their immutable JSON artifacts to `VR-10`, then writes `<version>/manifest.json` and `<version>/report.md` below the external artifact directory.

The report counts passed, failed, unavailable, uncaptured, and unrecognized check results separately.

Each report row includes its exact result.

The requirement total counts only rows backed by passed checks and present artifacts.

A separate job that checks out no source and executes no package code computes a complete file index and signs a fixed-format candidate or final endorsement with Ed25519.

The endorsement binds phase, repository, exact commit, version, and the SHA-256 of the complete file index.
Expand Down Expand Up @@ -659,6 +671,12 @@ Tangle, supervisor, and live-analysis commands return a typed unavailable result

The release workflow uses `pnpm release:prepare`, `pnpm release:collect`, and `pnpm verify:candidate` before publication.

The candidate job supplies Bridge, eval, Tangle, analysis, and supervisor settings only to the complete release-check step.

Inference, sandbox, and analysis use separate GitHub environment secrets.

Endpoints, models, providers, runners, and supervisor identifiers remain environment variables.

After publication it uses `pnpm release:record-publication` and `pnpm verify:release`; these workflow commands are not additional check records.

## Verification acceptance
Expand Down
116 changes: 115 additions & 1 deletion scripts/live-required.test.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import assert from 'node:assert/strict'
import { execFileSync } from 'node:child_process'
import { chmod, mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'
import { access, chmod, mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join, resolve } from 'node:path'
import { pathToFileURL } from 'node:url'
Expand All @@ -21,6 +21,7 @@ import {
} from './live-required/contracts.mjs'
import {
closeSession,
initializedSession,
prepareProductionWorkspace,
runHeadlessTurn,
} from './live-required/headless.mjs'
Expand Down Expand Up @@ -49,6 +50,9 @@ function protectedEnvironment() {
'BRAID_TANGLE_SANDBOX_MODEL',
'BRAID_TANGLE_SANDBOX_RUNNER',
'BRAID_TANGLE_SANDBOX_CREDENTIAL_REF',
'BRAID_TANGLE_SANDBOX_AUTH',
'BRAID_TANGLE_SANDBOX_API_KEY',
'BRAID_TANGLE_SANDBOX_BEARER',
'BRAID_TANGLE_LIVE_ADAPTER',
'BRAID_SUPERVISOR_ROOT',
'BRAID_SUPERVISOR_ID',
Expand Down Expand Up @@ -411,6 +415,116 @@ test('configured headless checks execute the real Braid RPC process and validate
}
})

test('production live workspaces remove every raw authentication alias from child processes', async () => {
const secret = 'live-required-child-secret-canary-991f'
const authenticationNames = [
'BRAID_ANALYSIS_AUTH',
'BRAID_ANALYSIS_API_KEY',
'BRAID_ANALYSIS_BEARER',
'BRAID_CLI_BRIDGE_AUTH',
'BRAID_CLI_BRIDGE_BEARER',
'BRAID_TANGLE_AUTH',
'BRAID_TANGLE_API_KEY',
'BRAID_TANGLE_BEARER',
'BRAID_TANGLE_SANDBOX_AUTH',
'BRAID_TANGLE_SANDBOX_API_KEY',
'BRAID_TANGLE_SANDBOX_BEARER',
]
const environment = Object.fromEntries(authenticationNames.map((name) => [name, secret]))
const config = await prepareProductionWorkspace({
repository,
environment,
kind: 'credential-scrub-test',
endpoint: 'https://router.tangle.tools',
model: 'openai/gpt-5',
runner: 'pi',
provider: 'tangle',
credentialRef: 'credential-ref-live-required-test',
})
try {
for (const name of authenticationNames) assert.equal(config.environment[name], undefined, name)
assert.equal(Object.values(config.environment).includes(secret), false)
} finally {
await config.cleanup()
}
})

test('generated live credentials use the same protected store as production Braid', async () => {
const secret = 'live-required-protected-store-canary-6c2f'
const config = await prepareProductionWorkspace({
repository,
environment: protectedEnvironment(),
kind: 'tangle-inference',
endpoint: 'https://router.tangle.tools',
model: 'glm-5.2',
runner: 'cli-base',
provider: 'tangle',
credentialValue: secret,
})
let session
try {
assert.equal(Object.values(config.environment).includes(secret), false)
assert.match(config.connection.credentialRef, /^credential-live-tangle-inference-/u)
const initialized = await initializedSession(
join(repository, 'dist', 'bin', 'braid.js'),
config,
)
session = initialized.session
assert.equal(initialized.state.view.connection, config.connection.name)
assert.equal(initialized.state.view.runner, 'cli-base')
assert.equal(initialized.state.view.model, 'glm-5.2')
} finally {
if (session !== undefined) await closeSession(session)
await config.cleanup()
}
})

test('generated credential cleanup reports failure and succeeds when retried', async () => {
let removeAttempts = 0
let disposeCalls = 0
const config = await prepareProductionWorkspace({
repository,
environment: protectedEnvironment(),
kind: 'cleanup-retry',
endpoint: 'https://router.tangle.tools',
model: 'glm-5.2',
runner: 'cli-base',
provider: 'tangle',
credentialValue: 'live-required-cleanup-retry-canary-822f',
credentialContextFactory: () => ({
store: {
async store(input) {
return input.ref
},
async remove() {
removeAttempts += 1
if (removeAttempts === 1) throw new Error('transient removal failure')
},
},
dispose() {
disposeCalls += 1
},
}),
})

await assert.rejects(
() => config.cleanup(),
(error) => error?.code === 'PROTECTED_CREDENTIAL_CLEANUP_FAILED',
)
await access(config.root)
assert.equal(removeAttempts, 1)
assert.equal(disposeCalls, 0)

await config.cleanup()
await assert.rejects(() => access(config.root), /ENOENT/u)
assert.equal(removeAttempts, 2)
assert.equal(disposeCalls, 1)

await config.cleanup()
assert.equal(removeAttempts, 2)
assert.equal(disposeCalls, 1)
})

test('configured real-path assertion failures emit failed release evidence', async () => {
const root = await mkdtemp(join(tmpdir(), 'braid-live-required-failure-test-'))
const wrapper = join(root, 'fixture-wrapper.mjs')
Expand Down
Loading