Skip to content

feat(install): install Pi when missing, ask before replacing it - #18

Closed
lucy-wolfe wants to merge 14 commits into
tribes-protocol:mainfrom
lucy-wolfe:feat/installer-manages-pi
Closed

feat(install): install Pi when missing, ask before replacing it#18
lucy-wolfe wants to merge 14 commits into
tribes-protocol:mainfrom
lucy-wolfe:feat/installer-manages-pi

Conversation

@lucy-wolfe

Copy link
Copy Markdown
Contributor

The installer printed a command for Pi and hoped. It handles it now.

state behaviour
Pi absent installed, no prompt
Pi below the floor Upgrade Pi to >= <floor>? [Y/n], default yes
Pi at or above the floor nothing, exactly as before

The asymmetry is the decision. An absent prerequisite is installed without asking, because chief cannot run a single person without it and there is nothing for the user to weigh. A Pi that is merely too old is their working tool, and replacing it is a different act — so that one asks. Declining exits nonzero with a clear message: a zero status would tell a calling script the box is ready when the runtime every person needs is too old.

The floor is read, never restated

It has one definition, in pi_floor.rs, and the repository's guard bans copying that number into scripts — rightly, because a copy is a second definition wearing a copy's clothes.

Verified before building on it: the release process already stamps it into the manifest as piFloor, and that is pinned by tests. The installer reads it from the manifest it has just unpacked, so a version bump touches no shell script. A release whose manifest carries no floor simply skips the check rather than guessing.

Two shell facts, both learned by running the branches

Both end the installer silently if got wrong, and neither is visible by reading.

A prompt in a piped script must read /dev/tty. This file is curl … | sh, so stdin is the script text — reading stdin would consume the rest of the installer.

And /dev/tty must be tested by opening it, inside a subshell. The device node exists in a container with no controlling terminal, so [ -r /dev/tty ] is true and the redirect then fails. Worse, the obvious guard is fatal:

if { : < /dev/tty; } 2>/dev/null; then   # ends the run, status 2, no message
if ( : < /dev/tty ) 2>/dev/null; then    # evaluates to false, as intended

: is a POSIX special built-in, and a redirection error on one is fatal to a non-interactive shell. It does not evaluate to false — it ends the installer. Measured twice: first as raw shell errors printed above the question, then as a trace stopping dead at the call.

Where there is no terminal, the default is taken and said, because a silent choice made on somebody's behalf is what surprises them later.

The other two failure modes

  • npm missing refuses cleanly with the existing die() style rather than half-installing.
  • A Pi that npm reports as installed is checked against the floor before being called ready. npm exiting zero is not the same as the floor being met — a global install can land later on PATH, or resolve to something still too old. Announcing "ready" without looking would be one more claim outrunning its evidence.

Nothing after chief itself is installed aborts the run, except the one deliberate decline.

Verified by running every branch

Absent Pi · at floor · above floor · below floor answering y · below floor with bare Enter · below floor answering n (exit 1, clear message) · below floor with no terminal · below floor with no npm · and a manifest carrying no floor at all.

The interactive cases were driven through a real pty, not simulated.

Coordination

The README currently documents installing Pi as step one, because until now the installer only warned. When this lands, that step becomes optional and the quick start collapses to a single curl.

Checks

sh -n install.sh — this file is piped straight into a shell, so a syntax error is a broken install rather than a failed test — plus all 98 repository guards and CI.

Lucy and others added 10 commits August 28, 2026 15:57
The word is a claim about whose fault the failure was, and the two invite
opposite recoveries: refused invites a corrected call, failed invites a retry.
Calling a crash a refusal is the worse direction, because it sends somebody to
fix a call that was never wrong.

The verb follows the classification rather than a list, so a refusal added next
month gets the right word without anyone remembering. One predicate in one
place: three renderers build a failure title, and two of them are bespoke
titles that would otherwise have kept saying failed for a refusal -- the
both-arms property again, found by sweeping for the word rather than trusting
the default path to be the only one.

One case was not what the classification said. A partial hire carries a status
so the card can name the people already hired, which a retry needs so it does
not double-hire them -- but the error it wraps may be a genuine crash. A status
carried for CONTEXT is not a claim about fault. That path now asks the wrapped
error's own type, and a producer in the same position marks itself rather than
being enumerated in the renderer, because a list is where the next case is
missed.

Pinned by a discriminating pair: a classified refusal is refused, an
unclassified failure is not, and a context-carried status with fault:true is
not. Verified by mutation -- making the predicate return true fails both
negative cases.

Checked the collapsed card: the verb is one character longer and the title is
not what gets truncated, so no card's summary window moves.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ed a default

The field was REQUIRED under a description opening 'DEFAULT: the department YOU
head'. An agent read that, reasoned correctly that it should omit the field,
met a schema that would not allow it, and improvised the most salient name in
context -- the company's. It obeyed the instrument over the claim, which is the
right thing for it to do.

The field is optional now and the default is resolved in execute using the
helper that already existed: the department the caller heads, or failing that
the one they sit in. That is word-for-word what the description promised, so
the promise was always implementable and simply was not implemented.

The worked example now shows the omitted form first. An example that keeps
passing the field re-teaches the habit the description is trying to correct.

The sweep asked the general question of every parameter in the file -- does the
schema permit what the prose promises -- across DEFAULT, omit, omitted,
optional and leave empty. Exactly one leaf field disagreed: this one. The only
other hit says 'never omit this field' and is required, so its prose and schema
agree. No mechanical guard was added: an instrument for a class of one is
furniture.

My first sweep reported sixteen mismatches. All but one were container objects
matching on a nested field's prose -- the detector measured a property adjacent
to the question. Refining it to leaf fields gave the real answer.

Also caught by an existing guard: the longer tool description exceeded the 1024
characters a strict provider accepts. Trimmed to 993 rather than expanded.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ng past a lecture

What it is in two sentences, the animation, install, and the first company
now lead the page. Depth moved into short skimmable sections or linked
docs: the crate table went to docs/ARCHITECTURE.md, which already carries
it, and the long GIF caption and quick-start asides were trimmed.

New: an Everyday commands table, derived from the routed operator verbs in
chief-cli's OPERATOR_VERBS, and a Contributing section naming the two open
workstreams (finishing apps/web, and multi-machine hosting so one busy
company stops pegging a single box's CPU) plus the issue tracker and
CONTRIBUTING.md.

Every command documented was checked against install.sh and the routed
verb table. The Pi floor line keeps Pi and 0.80.10 on one line for
pi-floor-single-definition.test.mjs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TMNbixeJo8rpeijUKyxhD2
Signed-off-by: Lucy Wolfe <lucy@zbox.sh>
The verb moved to the fault marker and the tag did not. The tag still measured
only the ABSENCE of a status, so a partial batch carrying one for context while
wrapping a real crash said "failed" -- correctly -- and then dropped the crash
marker. Somebody debugging that would see a list of people already hired, no
fault marker, and reasonably conclude they had passed bad input.

One classification, two surfaces. The tag now reads the same marker, and a test
asserts the two are complementary rather than merely each correct: nothing may
be both a refusal and a system fault, which fails if either rule moves without
the other. Verified by mutation -- putting the tag back on the old instrument
fails the crash case and only that case.

This was the scope note I flagged rather than fixed in the first pass. Review
ruled it fix-here, and that was right: shipping the verb on the new instrument
and the tag on the old one is a divergence nobody would find again except by
hitting it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The installer already edits the shell profile and reports exactly which
file it touched, so the extra line was a second copy of work the script
does itself.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TMNbixeJo8rpeijUKyxhD2
Signed-off-by: Lucy Wolfe <lucy@zbox.sh>
The animation speaks for itself; the recording provenance note is not
needed on the front page.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TMNbixeJo8rpeijUKyxhD2
Signed-off-by: Lucy Wolfe <lucy@zbox.sh>
Three steps a stranger can paste in order: Pi, chief, first company. The
Pi floor stays on one line with the name for the pi-floor guard.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TMNbixeJo8rpeijUKyxhD2
Signed-off-by: Lucy Wolfe <lucy@zbox.sh>
Step 3 says what the first run is (Founder, two questions, then the CEO
boots) and step 4 says what every later run is (the same bare chief
starts and attaches the company). The transition is the part a stranger
cannot guess from the command alone.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TMNbixeJo8rpeijUKyxhD2
Signed-off-by: Lucy Wolfe <lucy@zbox.sh>
…s Pi (#1)

The Pi step collapses into a single sentence: the installer installs a
missing Pi and asks before upgrading an old one. The version number
leaves the README entirely; the floor's single definition stays in
pi_floor.rs, and CONTRIBUTING.md remains the document that quotes it.

Depends on the installer change (PR tribes-protocol#18) riding in the same integration
branch; without it this page describes behavior install.sh does not have.


Claude-Session: https://claude.ai/code/session_01TMNbixeJo8rpeijUKyxhD2

Signed-off-by: Lucy Wolfe <lucy@zbox.sh>
Co-authored-by: Lucy Wolfe <lucy@zbox.sh>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Lucy and others added 3 commits August 28, 2026 17:32
A busy person now reads an ordinary message at the next step boundary inside
the running turn instead of at the end of it. The operator typing mid-turn has
always been submitted as a steering message and consumed within seconds;
ordinary mail rode the follow-up queue, which Pi consumes only once the agent
has no more tool calls or steering messages. So somebody an hour into a piece
of work did not see a teammate's message until the hour was over, while the
identical words typed by the operator arrived immediately.

Interrupt mail already rode the steering lane, so this widens a
production-proven path rather than building one. The digest is untouched:
batching is still the answer to twenty messages in one turn, and only the
moment of consumption moved.

Verified before changing anything, because it would have been a real blocker:
the delivery receipt hangs off message_start, not turn_start, so it is written
wherever the message is consumed. Interrupts have been consumed mid-turn in
production all along and receipted through that same handler, so a normal
envelope takes an identical path.

Three call sites were considered and two moved. The work-resume prompt keeps
follow-up and says why: nobody is waiting on it and it asks the person to pick
their own work back up. The input-requeue rescue and the printed-tool-call
corrective keep theirs, with rationales already written down that remain true.

An unreachable ternary went with it: normal urgency is routed away three lines
above the interrupt delivery, so its false arm could never be taken even before
this change.

The change was unpinnable as written, so it was made pinnable. The delivery
table was already covered, but the mode passed into it was two string literals
at two call sites -- so the timing of every delivery in the product could change
without one test noticing, which is exactly what happened when the first
implementation went green. It is one named decision now, with a test that
asserts the rule and fails when the mode is reverted.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
feat(intercom): mail rides the same queue as the operator's own typing
The installer printed a command and hoped. It handles Pi now, and the asymmetry
is the decision: an ABSENT Pi is installed with no prompt, because chief cannot
run a single person without it and there is nothing to weigh; an EXISTING Pi
that is merely below the floor belongs to the user, and replacing a working
tool without asking is a different act, so that one prompts with a default of
yes. Declining exits nonzero -- a zero status would tell a calling script the
box is ready when the runtime every person needs is too old.

The floor is read from the release manifest, which the release process already
stamps from the single Rust definition. A version bump needs no edit here, and
the single-definition guard is satisfied rather than worked around.

Two shell facts, both learned by running the branches rather than reading them,
and both of which end the installer silently if got wrong:

A prompt in a script that is piped into sh must read /dev/tty, because stdin is
the script text and reading it would consume the rest of the installer.

And /dev/tty must be tested by OPENING it, inside a SUBSHELL. The node exists
in a container with no controlling terminal, so `[ -r /dev/tty ]` is true and
the redirect then fails; worse, `{ : < /dev/tty; }` uses a POSIX special
built-in, and a redirection error on one is fatal to a non-interactive shell --
it does not evaluate to false, it ENDS THE RUN with status 2 and no message.
Measured both: first as raw shell errors printed to the user, then as a trace
that stopped dead at the call.

npm missing refuses cleanly rather than half-installing, and a Pi that npm
reports as installed is checked against the floor before being called ready.

Verified by running every branch: absent Pi; at floor; above floor; below floor
answering y, bare Enter, and n (exit 1 with a clear message); below floor with
no terminal; below floor with no npm; and a manifest carrying no floor at all.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@lucy-wolfe
lucy-wolfe force-pushed the feat/installer-manages-pi branch from f73571c to 0a53ba2 Compare August 28, 2026 17:40
@lucy-wolfe

Copy link
Copy Markdown
Contributor Author

Merged into the v0.5.7 integration branch and shipping in #16. The commit is on that branch verbatim; GitHub could not retarget this pull request because a cross-fork PR's base must live in the base repository, and the integration branch lives on the fork. The review that happened here stands.

@lucy-wolfe lucy-wolfe closed this Aug 28, 2026
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