Skip to content

fix+docs: v0.3.0 live-acceptance fixes and behavior contracts - #142

Merged
xiaoland merged 37 commits into
mainfrom
fix/setup-runtime-version
Sep 3, 2026
Merged

xiaoland merged 37 commits into
mainfrom
fix/setup-runtime-version

Conversation

@xiaoland

@xiaoland xiaoland commented Aug 31, 2026

Copy link
Copy Markdown
Owner

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

  • setup: adapter_version pinned the hardcoded value → validation rejected the generated config on newer runtimes; now pins the discovered version.
  • setup: profile workspace was never created (first materialization parked blocked); codex instance CODEX_HOME had no auth bootstrap. Setup creates the workspace, imports ~/.codex/auth.json or prints login guidance; doctor gains a Codex credentials check.
  • setup/docs: Issue activation contract stated (trusted @braid mention; native assignment is GitHub-side Agent App provisioning).
  • github: installation client pinned the initial 1-hour token → permanent 401s after expiry; now auto-refreshing installation auth, plus exponential backoff in mention-authority resolution.
  • worktree: libgit2 fetch/worktree-add broke behind proxies and on remote-tracking refs; fetch + worktree add now shell out to tools.git.

Behavior contracts (docs only, no code yet)

  • Publication Discretion: a delivered comment/mention never obligates a public reply; silence is valid; private reasoning lives only in the physical session and dies on Context Reset. The Braid System Prompt must state this.
  • Platform-neutral internal event model: typed EventKind owned by protocol.rs; producers map platform deliveries, queue/group consume EventKind only. Rulings: first trusted mention on a dormant Work Item IS the internal assign event; a mention on a closed Work Item does not wake the sleeping group.
  • Issue Agent worktree takeover: every Agent Group session runs in a dedicated generation-scoped worktree (issue: sole Development linked branch, else default origin branch; several = ambiguous → blocked). PR behavior unchanged.

fmt/check/clippy/test green; each runtime fix exercised live during acceptance.

…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.
@xiaoland xiaoland changed the title fix(setup): pin profile adapter_version to the discovered runtime version Fix series issues exposed by the real manual smoke test Aug 31, 2026
@xiaoland xiaoland changed the title Fix series issues exposed by the real manual smoke test Fix series of issues exposed by the real manual smoke test Aug 31, 2026
…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.
@xiaoland xiaoland changed the title Fix series of issues exposed by the real manual smoke test fix: v0.3.0 live-acceptance fixes (setup, token refresh, worktree) 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.
@xiaoland xiaoland changed the title fix: v0.3.0 live-acceptance fixes (setup, token refresh, worktree) fix+docs: v0.3.0 live-acceptance fixes and behavior contracts Sep 1, 2026
- 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.
@xiaoland
xiaoland merged commit e27cf7f into main Sep 3, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant