Bridge Operations
September 25, 2026 · View on GitHub
Session Start
Phase 0 — Detection (always first)
Before answering any user message at session start, run the detection gate
in rules/session-start.md. It checks branch,
user/* existence, and bridge-config.yaml presence, and routes to
onboarding, branch-switch, orphan-state handling, or normal load. Do not
skip this phase — not even for generic greetings.
Phase 1 — Work-system load
Only runs when Phase 0 returns NORMAL and work.enabled: true in the
session slice of bridge-config.yaml. Read that slice with
python3 scripts/bridge-config.py --session, which emits the six blocks the
session load needs; the other fifteen belong to the skill that owns them and are
read when that skill runs. Reading the whole file to reach work.enabled is
what the slice exists to stop:
- Read the recent slice of the work log with
python3 scripts/worklog.py --recent 3(the week header, the rolling TODO and the three most recent day blocks) pluswork/board.md(active tasks —work/tasks/finite,work/streams/long-running). Reading the whole log to reach the last activity is what the slice exists to stop: on one live instance that file was 405,076 bytes./briefingand/archivestill read it in full, which is what they are for - Create today's day-block if missing (from
work/templates/day.md; header## {Weekday} DD.MM) - Read the registry index:
python3 scripts/context-index.py ecosystem.yaml(settings verbatim, one line per repo/customer/workspace). Fetch an entry with--get <name>when the work names one. Skip silently when the file is absent, which is the normal state before onboarding - Read the memory index:
python3 scripts/memory-location.py index, andget <name>for one fact when the work names it. This is how the memory base reaches a harness that reads onlyAGENTS.md; inside Claude Code with auto memory on it prints a one-line note, since the harness already loaded that index. Prints nothing when there is no memory base yet (docs/memory.md§ Reading it on any harness) - Load standing orders: run
python3 scripts/standing-orders.py --indexand read the bodies it markseager. For the rest the index carries a summary and a trigger vocabulary; read that body when its vocabulary comes up. The index is computed at the moment of use, so it is never stale - Check CORE updates:
git log HEAD..main --oneline— offer merge if new - On "continue", "morning", "status": show summary, don't ask questions.
When
bridge-config.yamlpurpose.statementis non-empty, lead the summary withThis Bridge is for {statement}.so the session opens oriented around the instance's north-star. Empty statement → omit the line (today's behaviour).
Commit Hygiene
Conventional commits + release impact
Repos with an automatic release workflow (.github/workflows/release.yml, e.g.
open-bridge) compute the next version from the merged commit subject — and a
squash-merge turns the PR title into that subject. The branch name never
enters into it. So two disciplines, every commit:
- Pick the type from the actual change class, not by habit:
feat:= a new capability ·fix:= a behaviour/bug change ·docs:= docs/text only ·chore:/ci:/refactor:/test:/style:/build:= supporting change. The type drives the release —feat:→ minor,fix:→ patch, everything else → no release (full mapping:docs/releasing.md). - Name the branch to match the type —
docs/…,fix/…,feat/…. It has no effect on the release, but an incoherent prefix (afeat/branch carrying adocs:change) forces the merger to double-check what will ship.
Keep the minor digit meaningful: while in 0.x, reserve feat: (→ minor) for
genuine product capabilities. Site/marketing/visual tweaks are docs:/chore:
(no release), not feat: — otherwise the version number runs ahead of real
maturity.
CORE/USER Separation
Before committing, verify paths match the branch:
On user/{name} branch: all paths allowed.
On main (or preparing /promote) — Scope-Routing:
Four scope tiers control which upstream a path can land on. Scope comes from
frontmatter (scope: core | org | personal | user | private) — skills nest
it under metadata: (metadata.scope), sub-agents and rules keep it
top-level. For the cluster-wrapper config paths
(identity/{personas,mandants,accounts,contracts}, infra/channels,
workflow/{contexts,projects}) a frontmatter scope: wins over the path
default; with no frontmatter the path decides (defaulting these to user — the
fail-safe: a missing or mistaken tag never leaks upward, it just fails to
promote). personal is an optional fourth tier — a private overlay under your
own account, separate from your org's overlay (see the § Tier Model).
| Scope | Allowed upstream | Path examples |
|---|---|---|
core (or unset) | open-bridge + your org overlay | CLAUDE.md, README.md, CONTRIBUTING.md, docs/, skills/ (metadata.scope: core), .claude/skills/, .claude/agents/ (scope: core or unset), .codex/hooks.json, .vibe/hooks.toml, rules/.md (top-level only = CORE tier; org/user rules live in rules/org/ + rules/user/ — see those rows), identity/{personas,accounts,mandants,contracts}/{_schema,_template}.yaml, infra/{remotes,channels,backups,instances}/{_schema,_template}.yaml, workflow/{calendars,contexts,projects}/{_schema,_template}.yaml, WRAPPER_TESTS_CORE (a schema's own contract suite ships with the schema; the list is enumerated because a fixture may hold instance data until its suite is generic), themes/, trackers/, scripts/** (ALLOWLIST, not a glob: the authoritative set is SCRIPTS_CORE_ALLOWLIST in scripts/categorize-commits.py; a new core script is registered there deliberately), scripts/tests/**, .pre-commit-config.yaml, .github/workflows/validate.yml, protocols/standing-orders/ |
org | your org overlay ONLY (never open-bridge) | skills/customer-a-coordinator/ (= metadata.scope: org), .claude/agents/{customer-a-,network-}.md, ecosystem.yaml, ecosystem.scope: org), identity/mandants/org.yaml |
personal | your personal overlay ONLY (never open-bridge, never your org overlay) | cluster-wrapper config carrying scope: personal — identity/{personas,mandants,accounts,contracts}/ |
user / private | stays local (never any upstream) | bridge-config.yaml, cluster-wrapper config with scope: user or no frontmatter (identity/{personas,mandants,accounts,contracts}, infra/{remotes,channels}, workflow/{contexts,projects}), infra/instances/ |
Routing-logic for /promote (per commit, per file):
- Read
scope:frontmatter — skills:metadata.scope; sub-agents/rules: top-levelscope:(or infer from path → table above) - Route the commit to ALL upstreams that the scope allows:
core→ both open-bridge AND your org overlay (open-bridge first, then the overlay pulls)org→ your org overlay onlypersonal→ your personal overlay only (a private overlay under your own account)user/private→ stay local
- Mixed-scope commits are split — never push a commit with
org(orpersonal) content to open-bridge.
Language is a parallel tier rule: CORE (scope: core) is authored in
English; org/user tiers may stay in the author's language. See
rules/language-policy.md.
workflow/contexts/: special case (shipped-ignored, re-allowed per origin)
Routing contexts split by content, not by folder. The shipped root .gitignore ignores every
workflow/contexts/*.yaml except _template.yaml, for every clone, before anything has run
in it; a private origin re-allows its own by writing workflow/.gitignore
(scripts/user-data.py arm, docs/structure.md):
| File | Scope | open-bridge (public origin) | org overlay (private origin) | private (this repo) |
|---|---|---|---|---|
workflow/contexts/_template.yaml | core | tracked | tracked | tracked |
workflow/contexts/{customer-a,doc-system}.yaml | org | ignored (shipped .gitignore) | tracked (org-shared) | tracked |
workflow/contexts/<personal>.yaml | user | ignored (shipped .gitignore) | ignored (shipped .gitignore) | tracked |
In this instance (private origin) all contexts are tracked, since git serves as
offsite backup. In open-bridge (public OSS, itself a public origin) only _template.yaml
ships; any instance file that ended up there stays ignored, since no negation file is ever
written on a public origin. In your org overlay (org-internal, itself a private repo) the
org-shared contexts (customer-a, doc-system) are tracked, personal ones stay ignored the
same way.
The same per-origin policy applies to identity/personas/, identity/mandants/, and
workflow/projects/: run python3 scripts/user-data.py patterns to see the shipped block
rather than checking any repo's .gitignore by hand.
Repo-specific blocklist (in addition to path scope):
Even path-allowed files run through rules/promote-safety.md content scan,
per destination repo. open-bridge has the strictest blocklist
(no Org/customer/personal refs). Your org overlay allows customer refs but
blocks personal PII.
If a commit mixes scope tiers: split into separate commits.
If a commit mixes CORE and USER files: split into separate commits.
Content safety (in addition to path allowlist): even inside allowed
paths, content can leak user identity, customer names, or infrastructure
identifiers — especially inside "Example" blocks and render samples.
Before any cherry-pick, merge, or direct commit targeting main,
run the scan defined in rules/promote-safety.md.
Rationalizations like "it's only an example" are the exact failure
mode that rule exists to block.
Messages
- Prefix: feat, fix, refactor, docs, config
- Focus on "why" not "what"
- Don't bundle unrelated changes
Offering to Commit
After a logical unit of work: suggest committing ("Ready to commit these changes?"), show the files list. On a user branch, commit freely; on main, validate CORE-only paths first.
Context Switching
When switching to another repo:
- Read that repo's CLAUDE.md FIRST — every repo has its own conventions
- Check branch model (development vs main vs dev)
- Commit changes THERE, not in the bridge
- Return and log the cross-repo work in work/log.md
Work Logging
When
work.enabled: true, logging is MANDATORY and CONTINUOUS — not best-effort. Every substantive unit of work gets its ownwork/log.mdrow the moment it lands — in the same turn it happened, not batched at the end, not once per day. That covers: a code change or commit, a bug fixed, a decision made (+ the why), a finding worth keeping, a deploy/restart, an issue/PR/board operation. If you did work this turn and there is no row for it, the turn is not finished — append the row before you hand back. The tool-agnostic backstop is thescripts/hooks/pre-commithook (armed viacore.hooksPath=scripts/hooks): at every productive commit it prints a log reminder, the active-task list, and a WIP re-check — warn-only, never blocking. Theworklog-drift-check.shStop hook is a Claude-only reinforcement on top. Do not wait for either to nag — log as you go. The user should never have to ask "did you log that?". Whenwork.enabledis false, no logging is expected.
Mechanics under this gate:
Triggers: Log to work/log.md after git commits, command invocations, repo switches, significant findings, end of work blocks.
30-minute rule: If >30 min without logging, catch up immediately.
Board sync: work/board.md is generated from the task dirs — edit STATUS.md and regenerate; never hand-curate the board.
WIP warning: If doing + review tasks in work/tasks/ >= max_active, warn only (never blocks) and suggest closing, reprioritising, or reclassifying. Long-running streams live in work/streams/ and do not count toward the limit.
Full work-system semantics — log format, logging levels, and the task lifecycle — live in docs/work-system.md.
Completion landing
At completion, do not leave work stranded on orphan feature branches or
unmerged PRs — drive it to the repo's default branch, determined
live, never assumed (gh api repos/X --jq .default_branch; e.g.
<you>/<your-bridge> = user/<name>, open-bridge = main, an org
overlay = development). The landing step then forks on what the default
actually is:
- Default is a personal user branch (e.g. your own Bridge instance =
user/<name>): commit + push there directly, no gate. This is the normal Feature-/USER-branch push that is already allowed (seeauto-end-of-work-cyclebelow). - Default is a SHARED branch (
main/developmenton the upstreams): drive the PR toward merge, but the merge itself stays announced and GATED — never merge or push tomain/developmentwithout explicit OK. This preserves the global hard rule; the only change is that the default expectation shifts from "park the PR, the user merges later" to "land it" (you actively push it to done rather than leaving it open).
After an approved merge: sync local clones to the default
(git checkout <default> && git pull) and delete stale feature branches.
Auto-end-of-work cycle (normal Feature-/UAT-work): when a unit is done
and verified, run the cycle yourself without being asked — deploy/restart
the affected service and verify it runs, document (STATUS.md +
work/log.md + relevant repo docs), commit + push the whole work/ folder
plus your own files to the Feature-/USER-branch only when origin is a
private repo you own; never push a user/* branch to a public/upstream
origin (push-guard.md). Stage atomically (intended
paths only, never sweep in unrelated in-flight changes), then confirm
briefly (commit hashes + service state).
Hard gates stay regardless of the above: no push to main/development;
no push of a user/* branch (or USER content) to a PUBLIC/upstream origin —
gate on origin visibility, not just the branch name (a user/* push is not a
main push but is the worse leak; resolve gh repo view --json visibility and see
push-guard.md); no merge, no real Prod deploy, no outward-facing
action (live number, dry_run=false, secret rotation) without explicit OK.
Maestro exception: a real Maestro mission (P3+) overrides the auto-commit — nothing the mission produces is committed or pushed until the user's end-approval. The conductor prepares; the human lands.
Pre-"done" independent review
Before declaring something done / launch-ready / consistent, run one independent unframed review pass: agents that judge fresh, briefed "assume nothing is intentional, report everything" ("nimm nichts als intentional an"). A framed audit, briefed with your own preloaded "ground truth," only checks against your assumptions and dismisses the exact errors you got wrong; an unframed pass checks the assumptions themselves — good for finding your own thinking errors, where a framed audit is only good for fixing-against-spec. This is the active-verification complement to SOUL § Verify before claim.