fix+docs: v0.3.0 live-acceptance fixes and behavior contracts - #142
Merged
Merged
Conversation
…sion build_config hardcoded adapter_version (codex-cli 0.147.0-alpha.6.5 / pi 0.84.3) while the runtime entry carries the actually discovered version; config validation requires the two to match, so setup failed for anyone whose local runtime had moved past the hardcode (e.g. codex 0.151.0). Use runtime.version, which is exactly what validation compares against.
Two more fresh-setup gaps found by real acceptance on 0.3.0:
- setup wrote profile.workspace into the config but never created the
directory; the first assignment materialization failed ('Profile
workspace does not exist') and parked the assignment in 'blocked'.
Setup now creates every profile workspace it writes.
- Codex runs with an instance-scoped CODEX_HOME, so global ~/.codex
credentials never applied and serve ran with the provider perpetually
disconnected. Setup now imports ~/.codex/auth.json into the instance
provider home when present, else prints explicit login instructions.
doctor gains a 'Codex credentials' check so the gap is caught before
serving.
…gent assignment) Real acceptance surfaced the confusion: users look for the App in the assignee picker. Native assignment is GitHub-side Agent App provisioning (verified empirically: the bot is absent from the assignees list and addAssigneesToAssignable fails FORBIDDEN 'cannot be assigned to issues or pull requests'); no permission or manifest field provides it. The trusted @braid mention is the only activation mode for ordinary Apps — the PRD already models this. State it in the setup output and user manual.
…backoff Found by live acceptance: one hour after serve start, every GitHub API call began failing 401 'Bad credentials' — the installation client was built once with a fixed personal_token (installation tokens expire after 1h) and never refreshed, silently wedging mention resolution, reactions, and the write outbox. The client now uses octocrab's installation auth state, which caches and refreshes tokens with an expiry buffer. Also: mention-authority resolution retried every 250ms tick on persistent failure (~4 rps against a dead credential); it now backs off exponentially (2s doubling to a 60s cap) and stops iterating the batch on first error.
Found by live acceptance: PR worktree provisioning failed with libgit2 'no TLS stream available' on a machine behind a local proxy, and libgit2 fetch has no credential-helper integration for private repositories. The network fetch now shells out to the configured tools.git (honors credential helpers and proxies); libgit2 keeps the local operations (ref lookup, worktree add).
libgit2's worktree add rejects remote-tracking references ('reference is
not a branch'); the system git creates the generation-scoped local branch
and the worktree in one step (-B for idempotent retries). git2 remains for
local inspection only.
This was referenced Aug 31, 2026
A delivered comment/review/mention never obligates a public reply; silence is a valid turn outcome. Private reasoning lives only inside the physical provider session, is never published by Braid, and dies on Context Reset, so durable conclusions must reach GitHub. The versioned Braid System Prompt must state this explicitly.
protocol.rs owns the typed EventKind contract (assign/unassign/mention/ wake/invalidate/lifecycle/origin_echo/noop); producers translate platform deliveries at ingress and queue/group consume EventKind only. GitHub mapping table added, including: the first trusted mention on a dormant Work Item IS the internal assign event (unifying mention activation with native Agent App assignment), and a mention on a closed Work Item is a mention, not an assign - closed groups do not wake, reopen is the designed re-entry.
Every Agent Group session runs in a dedicated generation-scoped worktree, not the shared profile workspace. Issue Agent worktrees live at worktrees/issue-<number>/<profile>-g<generation> and bind the issue's sole same-repo Development linked branch when exactly one exists, otherwise the default origin branch; several Development branches are ambiguous and block materialization with an operator diagnostic (mirroring pr ensure). Profile workspace remains the clean source checkout. PR behavior unchanged.
- Publication Discretion reframed around the persistent private workspace: the worktree (not the session) holds private working state and survives session replacement within a generation; .braid/ in the worktree is git-excluded. No discard framing. - mention->assign stated as a consumption rule (kind stays mention; dormant-group consumption activates), not an EventKind mapping. - Issue worktree ambiguity: several Development branches fall back to the default branch instead of blocking; the system prompt tells the Agent it may switch/create branches.
Event ledger rows now carry a typed internal EventKind (assign/unassign/mention/wake/invalidate/lifecycle/origin_echo/noop) plus a semantic detail column (schema v2) instead of ad-hoc GitHub-shaped classification strings; producers map platform deliveries at ingress and consumers branch on kind/detail only. - cross_surface_invalidation folds into invalidate with detail='cross_surface'; the runnable-batch debounce gate is preserved - origin_echo and noop rows are evidence-only and consumed at ingest - trusted mention resolution reclassifies wake -> mention - activation candidates are kind-based; assign/mention on a non-open Work Item is consumed as a no-op (closed groups sleep until reopen) - reopen reactivation is idempotent: a busy group (materializing/active/finalizing) consumes the event instead of reviving a stale sleeping generation into the unique active index - braid gh pr ensure records an internal assign event - 00_clean_install backup assertion updated for the real v1->v2 upgrade
Every Agent Group session now runs in a dedicated generation-scoped worktree provisioned from the Profile's source checkout, never in the shared workspace itself: - issue worktrees live at worktrees/issue-<number>/<profile>-g<gen> and bind the issue's sole same-repository Development linked branch when exactly one exists; zero or several Development branches start on the repository default branch (the Agent may switch or create branches itself - the system prompt contract says so) - resume and reopen-reactivation restore the persisted worktree as the session cwd, mirroring the PR path; a missing/incompatible worktree blocks the session with an operator diagnostic - every provisioned worktree git-excludes .braid/ through the common git dir: the Agent's private persistent workspace (notes, drafts, scratch) survives session replacement within the generation and stays out of git status, commits, and GitHub
A turn fenced to 'unknown' at resume/restart left its provider session
in lifecycle 'unknown' forever; every group progress path (turn claims,
idle reset candidates) joins an idle session, so the group could never
make progress again. A session whose turn ended with an uncertain
outcome is now never reused: mark_turn_terminal('unknown') requests a
context reset (continuation=false, no events) so the existing reset
machinery lazily replaces the session with a fresh physical one on the
next group tick. Issue Context resets also restore the generation
worktree as the session cwd, mirroring the PR path.
…tent workspace Both role prompts now state explicitly: a delivered comment/review/ mention never obligates a public reply (silence is a valid outcome); the generation worktree is the Agent's private persistent workspace for notes and drafts under .braid/ (git-excluded), surviving provider session replacement within the assignment. The Issue prompt also gains the worktree cwd line with the Development-branch/default-branch rule and the freedom to switch or create branches.
An unassign event now settles after the scheduler quiet window (debounce for re-assignment flapping), is confirmed against canonical assignees, and retires the group: assignment/agent/sessions/worktrees to 'retired', pending batches consumed, an in-flight turn fenced 'interrupted' and best-effort interrupted through its session. The previously ignored unassign candidates now have a consumer.
One repository = one source checkout: profile.workspace is now optional and defaults to <instance>/source, shared by all profiles as the worktree provisioning source (Agent sessions never edit it; they run in provisioned worktrees whose .braid/ directory is their git-excluded private workspace). braid setup clones the configured repository into the source checkout (manual-clone guidance on failure, never overwrites a non-Git directory); braid doctor gains a Source checkout check (exists, is a Git clone, origin matches the configured repository).
Follows the root/database/backups precedent: an optional instance-level knob (instance == one repository) for where generation-scoped worktrees are provisioned, resolved against the config directory. New generations use the configured location; existing generations keep the worktree paths recorded in SQLite, so changing the knob never orphans a running group.
- record_agent_worktree dropped its PR-only work-item-kind constraint (predicates already pin the exact materializing agent/assignment), so Issue Agent worktree provisioning no longer parks assignments blocked. - Opening-body mentions now activate: mention detection scans issue and pull_request bodies in addition to comment/review bodies, and an opened-with-mention event is promoted from consumed-at-ingest noop to a pending mention so trusted-mention resolution can schedule it. Reconcile stays baseline-only for first observations, so historical issues with mentions never retro-activate.
- begin_work_item_reactivation no longer leaves the reopen event pending forever when a sleeping assignment's generation is not selectable (agent/assignment lifecycle divergence, e.g. from operator surgery); it consumes the event as a no-op so a later trusted mention can activate a fresh generation. - reconcile no longer skips work-item state transitions whose body digest survived unchanged: close/reopen/merge observations bypass the digest-based unchanged rule, so a close racing the reconcile read window cannot be silently absorbed by the local-state sync.
…orktree generations
v0.3.0-era generations preserved no worktree, so reopening their issue
parked the revived assignment blocked ('no preserved worktree').
Reactivation now provisions a fresh generation worktree on the current
head ref when nothing was preserved. The no-revivable-generation branch
of begin_work_item_reactivation now consumes the reopen event
unconditionally: selection is deterministic on durable state, and
leaving it pending wedged the dispatch loop in a silent 250ms spin.
prepare_work_item_finalization kept close events pending whenever a sleeping assignment existed but nothing was active to finalize, spinning the dispatch loop forever. Pending is now reserved for groups that are materializing/finalizing (about to become selectable); dormant groups consume the close as a no-op.
complete_work_item_reactivation only replaced 'sleeping' sessions, so a
stale 'idle'/'unknown' session from a diverged generation survived and
the turn claimer bound the post-reactivation wake turn to a session the
new epoch had never started ('no AgentSession found for claimed turn').
Any pre-existing session of a reactivated agent belongs to the previous
epoch and is now replaced regardless of its non-running state.
A turn fenced 'unknown' (crash, restart, dead connection) consumed its wake batch but its Human-visible request was silently dropped: the reset claim rebuilt the session with continuation=false and nothing re-drove the batch. The fenced turn's batch events now return to pending and are re-scheduled, so the replacement session re-drives the inputs after the reset lands. A rare duplicate reply is absorbed by GitHub-write idempotency; silently losing a delivered request is the worse failure.
…rdict An incompatible or unworktreed persisted session was blocked without fencing its crashed epoch's in-flight turn, leaking a 'running' turn that wedged later claims. The fence now runs first. Block branches also log the exact incompatibility reason (previously silent).
All suite polling loops keep the 120-second default but accept an operator override; heavily loaded operator machines otherwise exhaust the readiness budget while the managed tunnel is still converging.
/app/hook/deliveries is cursor-paginated and rejects the offset-style page key with 422, which broke 'braid gh deliveries' and any suite phase that lists App deliveries.
A lost provider connection epoch only ever surfaced 'reconnecting', so operators (and the Slice 3 gate) could not observe real app-server process loss. The disconnect path now marks the provider unavailable until the next connect/resume succeeds; convergence errors keep their precise message.
The provider is the authority on which models it accepts; the llm_providers catalog only carries cost metadata. Hard-rejecting an uncatalogued model blocked operators from using new provider models and prevented real provider turn failures from being exercised end to end.
The System Prompt stated facts ('.braid/ is excluded from git') and then
prescribed a workflow ('keep working notes there'). Keep the facts -
volatile provider context, persistent worktree, git-excluded .braid/ -
and drop the prescription; the agent decides how to cope with volatility
itself (Codex already compacts on its own). The Issue prompt also stops
explaining branch provenance; the agent can discover worktree state.
Docs reworded to describe the mechanism without prescribing usage.
GitHub's comments-list read can briefly lag the reactions read after a turn terminal; the one-shot final recheck failed on a stale read even though the Agent had published the marker comment and the loop had seen the +1.
Suite 40 predates the braid gh publish channel and filtered Agent comments by the operator's gh login. Agent comments are App-authored by contract now, so the marker/comment counters use the App actor like suite 30 does.
The app-server child lookup used awk with an early exit, SIGPIPE-ing ps under 'set -o pipefail' and killing the suite mid-phase (exit 141). The turn-mirror assertion now excludes App-authored Agent comments and Operational Status comments instead of requiring zero App comments.
The fmt layer defaulted to stdout, polluting machine-readable CLI output (telemetry probe --json) with log lines and making the Slice 6 sampling check unparseable.
The Quick Tunnel banner precedes Cloudflare's DNS record for the fresh hostname; probing at banner time primed negative caches (system and upstream) that outlived the probe loop. Wait 20s after registration before the first lookup, and allow five candidates.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
One branch carrying everything from the v0.3.0 live acceptance run on braid-poc-test: six runtime fixes plus the three behavior-contract documents drafted from the behavior review.
Runtime fixes
adapter_versionpinned the hardcoded value → validation rejected the generated config on newer runtimes; now pins the discovered version.blocked); codex instanceCODEX_HOMEhad no auth bootstrap. Setup creates the workspace, imports~/.codex/auth.jsonor prints login guidance; doctor gains aCodex credentialscheck.@braidmention; native assignment is GitHub-side Agent App provisioning).tools.git.Behavior contracts (docs only, no code yet)
EventKindowned byprotocol.rs; producers map platform deliveries, queue/group consumeEventKindonly. Rulings: first trusted mention on a dormant Work Item IS the internalassignevent; a mention on a closed Work Item does not wake the sleeping group.fmt/check/clippy/test green; each runtime fix exercised live during acceptance.