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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
/eval
/flare
/gate
/org
/review
/reviewfindings
/runway
Expand Down
3 changes: 3 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ Orientation block you can point an agent at to ground it fast.
`gate` (the merge-authorization boundary — grants, the verifier ladder, the
hash-chained decision log; exit codes 0 pass / 1 blocked / 2 parked /
3 refused / 4 error are a load-bearing seam),
`org` (the Baton home — role continuity chains over `contracts/org`:
attach/claim/yield lifecycle, the byte-capped `org boot` re-entry index, and
the SessionStart/Stop hook scripts that wire sessions to roles),
plus `local`'s CLIs (`local`, `eval`).
- `docs/DESIGN.md` — the repo charter. `FOLLOWUPS.md` — the lazy-migration queue
and deferred decisions (the engineering debt this codebase owes).
Expand Down
3 changes: 3 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ Orientation block you can point an agent at to ground it fast.
decision for a parked escalation and drives `gate resolve` to close the
agent→human→agent loop, shelling gate and never importing it; a contract+seam,
not a plane — see `docs/features/escalation-plane/spec.md`),
`org` (the Baton home — role continuity chains over `contracts/org`:
attach/claim/yield lifecycle, the byte-capped `org boot` re-entry index, and
the SessionStart/Stop hook scripts that wire sessions to roles),
plus `local`'s CLIs (`local`, `eval`).
- `docs/DESIGN.md` — the repo charter. `FOLLOWUPS.md` — the lazy-migration queue
and deferred decisions (the engineering debt this codebase owes).
Expand Down
52 changes: 52 additions & 0 deletions cmd/org/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# org — the Baton home

The runtime for role continuity chains. The kernel — record spine, kind set,
fold, every admission law — is `contracts/org` and is imported as types and
laws, never re-decided here. This tool owns what a pure kernel cannot: where
chains live, when records are stamped and locked, and how a fresh session
re-enters a role.

Design context: `docs/features/org/vision.md` (PR #245, the org TDD). The
system name there is Baton; this binary is its first runtime slice.

## What it is

- **State**: `$ORG_STATE` (default `~/dev/org/state`) holds one JSONL chain
per role at `<tenant>/<role-with-colons-as-->/chain.jsonl`, plus
content-addressed erasable bodies under `blobs/`. Appends are serialized by
an flock over the fold→admit→append critical section; admission is
`org.Advance`, so nothing reaches a chain that the kernel would refuse.
- **Verbs** map one-to-one onto record kinds (charter, attach, claim, yield,
complete, abandon, assign, takeover, revoke, seal, note, checkpoint, …) plus
read verbs: `boot` (the byte-capped re-entry index), `status` (the board),
`log`, `verify`, `blob`.
- **Hooks** (`hooks/`): `sessionstart-boot.sh` injects `org boot` output into
a session whose cwd maps to a role (`$ORG_STATE/roles.map`);
`stop-mark.sh` appends a mechanical `mark` when a session stops. Both
fail-open: no mapping, no binary, no chain — exit 0, empty output.

## Invariants

- The home adds no judgment. A record refused by the kernel is refused here
with the kernel's reason on stderr; the chain does not grow.
- Checkpoints are distilled by a host, never demanded of the working agent.
The Stop hook writes a `mark`; a mark at the tip renders the boot index
`degraded`, which is the honest state.
- The boot index is an index: pointers plus hooks, byte-budgeted
(`-max-bytes`, default 2048), shedding depth (last-word excerpt, held list)
but never the headline, the charter line, or a dangling obligation.
- Liveness is derived from the writer's own declared `next_due`, never from
self-report at read time.

## Exit codes (load-bearing seam)

0 ok · 1 kernel refusal (stderr carries the reason id, e.g. `dangling_claim`)
· 2 usage · 4 error. A refusal is the substrate working, not a failure.

## Checks

```
gofmt -l ./cmd/org && go vet ./cmd/org/...
golangci-lint run ./cmd/org/...
go test ./cmd/org/...
```
52 changes: 52 additions & 0 deletions cmd/org/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# org — the Baton home

The runtime for role continuity chains. The kernel — record spine, kind set,
fold, every admission law — is `contracts/org` and is imported as types and
laws, never re-decided here. This tool owns what a pure kernel cannot: where
chains live, when records are stamped and locked, and how a fresh session
re-enters a role.

Design context: `docs/features/org/vision.md` (PR #245, the org TDD). The
system name there is Baton; this binary is its first runtime slice.

## What it is

- **State**: `$ORG_STATE` (default `~/dev/org/state`) holds one JSONL chain
per role at `<tenant>/<role-with-colons-as-->/chain.jsonl`, plus
content-addressed erasable bodies under `blobs/`. Appends are serialized by
an flock over the fold→admit→append critical section; admission is
`org.Advance`, so nothing reaches a chain that the kernel would refuse.
- **Verbs** map one-to-one onto record kinds (charter, attach, claim, yield,
complete, abandon, assign, takeover, revoke, seal, note, checkpoint, …) plus
read verbs: `boot` (the byte-capped re-entry index), `status` (the board),
`log`, `verify`, `blob`.
- **Hooks** (`hooks/`): `sessionstart-boot.sh` injects `org boot` output into
a session whose cwd maps to a role (`$ORG_STATE/roles.map`);
`stop-mark.sh` appends a mechanical `mark` when a session stops. Both
fail-open: no mapping, no binary, no chain — exit 0, empty output.

## Invariants

- The home adds no judgment. A record refused by the kernel is refused here
with the kernel's reason on stderr; the chain does not grow.
- Checkpoints are distilled by a host, never demanded of the working agent.
The Stop hook writes a `mark`; a mark at the tip renders the boot index
`degraded`, which is the honest state.
- The boot index is an index: pointers plus hooks, byte-budgeted
(`-max-bytes`, default 2048), shedding depth (last-word excerpt, held list)
but never the headline, the charter line, or a dangling obligation.
- Liveness is derived from the writer's own declared `next_due`, never from
self-report at read time.

## Exit codes (load-bearing seam)

0 ok · 1 kernel refusal (stderr carries the reason id, e.g. `dangling_claim`)
· 2 usage · 4 error. A refusal is the substrate working, not a failure.

## Checks

```
gofmt -l ./cmd/org && go vet ./cmd/org/...
golangci-lint run ./cmd/org/...
go test ./cmd/org/...
```
62 changes: 62 additions & 0 deletions cmd/org/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# org

The Baton home: role continuity chains for agent sessions. A **role** is a
durable office (`lead:agentic-development`) with an append-only hash chain; a
**session** is a disposable incarnation that attaches to it, acts, and leaves
a record. The next session starts where the last one stopped, and two
sessions cannot silently reach different conclusions about the same thing —
the chain's compare-and-swap refuses the second writer.

The kernel (record spine, state machine, admission laws, the fold) is
[`contracts/org`](../../contracts/org); this binary is its runtime.

## Quickstart

```sh
go install ./cmd/org

# the operator charters a role once
org charter -role lead:agentic-development \
-scope dossier:org -scope github:itsHabib/workbench \
-tier T2 -supervisor human:mh -cycle-ceiling 3 \
-retire-when "org loop merged into steward"

# a session becomes the incarnation, works, and leaves a record
org attach -role lead:agentic-development -next-due 4h
org assign -role lead:agentic-development -work dossier:org/p1/t3 -pin "task body"
org claim -role lead:agentic-development -work dossier:org/p1/t3
org yield -role lead:agentic-development -work dossier:org/p1/t3 -body "where I stopped"
org checkpoint -role lead:agentic-development -body "SESSION END: …"

# the next session reads the index the last one left
org boot -role lead:agentic-development
org status
```

`org boot` is the re-entry surface: a byte-capped index (default 2048) of the
role's charter, held work, obligations, liveness, and the last incarnation's
final word — pointers with hooks, not a context dump. Depth is read lazily
(`org blob <digest>`, `org log`).

Refusals are the substrate working: claim work you don't hold → exit 1,
`work_not_held`. A supervisor `takeover` mid-claim leaves a **dangling
obligation** the successor must discharge before claiming anything — silent
disappearance of work is not representable.

## Harness wiring

Two hooks close the loop for Claude Code sessions (both fail-open):

- `hooks/sessionstart-boot.sh` — injects `org boot` into a fresh session when
its cwd maps to a role in `$ORG_STATE/roles.map`
(`<path-prefix> <tenant> <role>`, longest prefix wins).
- `hooks/stop-mark.sh` — appends a mechanical `mark` when a session stops;
the next boot renders `degraded` until someone distills a checkpoint.

Install snippets are in each script's header. State lives at `$ORG_STATE`
(default `~/dev/org/state`).

## Exit codes

`0` ok · `1` the kernel refused the record (stderr names the reason) ·
`2` usage · `4` error.
46 changes: 46 additions & 0 deletions cmd/org/hooks/sessionstart-boot.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#!/usr/bin/env bash
# SessionStart hook: inject a role's boot index into a fresh session.
#
# The session's cwd is mapped to a role via $ORG_STATE/roles.map, one line per
# binding:
#
# <path-prefix> <tenant> <role>
# /Users/mh/dev/workbench mh lead:agentic-development
#
# Longest matching prefix wins. No mapping, no org binary, no chain — all exit
# 0 with no output: re-entry is an offer, never a gate on starting a session.
#
# Install (in ~/.claude/settings.json under hooks):
# "SessionStart": [{"hooks": [{"type": "command",
# "command": "ORG_STATE=\"$HOME/dev/org/state\" bash \"$HOME/dev/workbench/cmd/org/hooks/sessionstart-boot.sh\"",
# "timeout": 5}]}]
set -euo pipefail

ORG_BIN="${ORG_BIN:-org}"
ORG_STATE="${ORG_STATE:-$HOME/dev/org/state}"
MAP="$ORG_STATE/roles.map"

command -v "$ORG_BIN" >/dev/null 2>&1 || exit 0
command -v jq >/dev/null 2>&1 || exit 0
[ -f "$MAP" ] || exit 0

input="$(cat)"
cwd="$(jq -r '.cwd // empty' <<<"$input" 2>/dev/null)" || exit 0
[ -n "$cwd" ] || exit 0

tenant="" role="" best=0
while read -r prefix map_tenant map_role; do
case "$prefix" in ''|'#'*) continue ;; esac
case "$cwd" in "$prefix"*) ;; *) continue ;; esac
if [ "${#prefix}" -gt "$best" ]; then
best="${#prefix}" tenant="$map_tenant" role="$map_role"
fi
done <"$MAP"
[ -n "$role" ] || exit 0

boot="$("$ORG_BIN" boot -state "$ORG_STATE" -tenant "$tenant" -role "$role" \
-max-bytes "${ORG_BOOT_BUDGET:-2048}" 2>/dev/null)" || exit 0
[ -n "$boot" ] || exit 0

jq -n --arg ctx "$boot" \
'{hookSpecificOutput: {hookEventName: "SessionStart", additionalContext: $ctx}}'
54 changes: 54 additions & 0 deletions cmd/org/hooks/stop-mark.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#!/usr/bin/env bash
# Stop hook: append a mechanical mark to the role's chain when a session ends.
#
# A mark is the host's observation, not the model's claim: session id, turn
# count, transcript path. It is deliberately NOT a checkpoint — distillation
# needs a model, and a record the working agent is required to write is a verb
# wearing a costume. The fold renders a mark tip as Degraded, which is the
# honest state: activity happened and nobody has distilled it yet.
#
# Same roles.map contract as sessionstart-boot.sh; fail-open throughout.
#
# Install (in ~/.claude/settings.json under hooks):
# "Stop": [{"hooks": [{"type": "command",
# "command": "ORG_STATE=\"$HOME/dev/org/state\" bash \"$HOME/dev/workbench/cmd/org/hooks/stop-mark.sh\"",
# "timeout": 10}]}]
set -euo pipefail

ORG_BIN="${ORG_BIN:-org}"
ORG_STATE="${ORG_STATE:-$HOME/dev/org/state}"
MAP="$ORG_STATE/roles.map"

command -v "$ORG_BIN" >/dev/null 2>&1 || exit 0
command -v jq >/dev/null 2>&1 || exit 0
[ -f "$MAP" ] || exit 0

input="$(cat)"
# Re-entrancy: a Stop hook that makes the agent continue re-fires Stop.
[ "$(jq -r '.stop_hook_active // false' <<<"$input" 2>/dev/null)" = "true" ] && exit 0
cwd="$(jq -r '.cwd // empty' <<<"$input" 2>/dev/null)" || exit 0
session="$(jq -r '.session_id // empty' <<<"$input" 2>/dev/null)" || exit 0
transcript="$(jq -r '.transcript_path // empty' <<<"$input" 2>/dev/null)" || exit 0
[ -n "$cwd" ] || exit 0

tenant="" role="" best=0
while read -r prefix map_tenant map_role; do
case "$prefix" in ''|'#'*) continue ;; esac
case "$cwd" in "$prefix"*) ;; *) continue ;; esac
if [ "${#prefix}" -gt "$best" ]; then
best="${#prefix}" tenant="$map_tenant" role="$map_role"
fi
done <"$MAP"
[ -n "$role" ] || exit 0

turns=""
if [ -n "$transcript" ] && [ -f "$transcript" ]; then
turns="$(jq -rs '[.[] | select(.type == "assistant")] | length' "$transcript" 2>/dev/null || true)"
fi

body="session ${session:0:8} stopped in $cwd"
[ -n "$turns" ] && body="$body after $turns assistant turns"
body="$body; transcript $transcript"

printf '%s' "$body" | "$ORG_BIN" mark -state "$ORG_STATE" -tenant "$tenant" \
-role "$role" -body - >/dev/null 2>&1 || exit 0
Loading
Loading