Documentation
¶
Overview ¶
The space-time advisor: a non-intrusive supervisor that, ONLY when a command fails, adds a context-aware hint explaining that the failure is determined by the agent's ambient resource environment ("space") — filesystem/disk, current working directory, CPU/memory, or network/locality — and what to do instead.
The goal is to stop the doomed retry loop an agentic tool (codex, claude, aider, …) burns time and tokens on when it cannot see WHY a command failed: e.g. an agent reaching `host.local` after the laptop moved off its LAN keeps probing IPs and assuming the local uid, all doomed, because the host is simply not on this network. Bashy uniquely holds the whole-environment snapshot and can say so.
It is wired as the OUTERMOST ExecHandler middleware (mirroring dryrun.go): it calls the rest of the chain, observes the resulting exit status, and on a non-zero exit runs a small pattern library against an injectable space snapshot. It NEVER blocks and ALWAYS returns the underlying error unchanged — it only appends one advisory line. Registered only by cmd/bashy (never the pure cmd/bash drop-in) and only in non-posix mode; active in agent mode or when BASHY_ADVISOR is set.
The space-time advisor's MEMORY: the "time" axis. The advisor accumulates what worked under which ambient conditions so it can turn a bare failure into a confident, history-grounded hint — the thing that makes bashy "know better" than a stateless agent looping in the dark.
Two layers, both self-contained (no dependency on the deferred Batch-1 audit journal):
- A per-session failure counter, for doomed-loop detection (case 5): when an agent retries the identical command and it keeps failing the same way, the advisor escalates ("change the approach, not the parameters").
- A best-effort persisted host-success ledger keyed by a NETWORK FINGERPRINT (a hash of the local subnets). It records that host X was reachable while the machine was on network A. When X later fails to resolve while the machine is on network B, the advisor can say "you reached X before from a different network — you're likely off its network now," which is far more actionable than a guess. The fingerprint is a RELATIVE signal (same network as before?), so no absolute home/remote classification is needed.
All persistence is best-effort: any IO error is swallowed, never surfacing to the shell. Disable persistence with BASHY_ADVISOR_NOMEM=1 (the in-memory layer still works); override the path with BASHY_ADVISOR_STATE.
Package agentos holds the AgentOS wiring that turns the shell core into the `bashy` system shell: the coreutils ExecHandler (so the pure-Go userland and the code-intel verbs run in-process) and the front-door subcommands (`bashy weave …`, `bashy otel …`, `bashy podman …`).
It is imported ONLY by cmd/bashy — never by cmd/bash. That is what keeps the two binaries independent: the pure `bash` drop-in's import graph never pulls in coreutils or any external-tool surface, so it stays a lean GNU Bash 5.3 drop-in, while `bashy` is the self-contained bootstrapper for a whole unix-like userland (bash + coreutils + pkg + external tools).
The bashy side of the Command Atlas (docs/command-atlas.md): merges what only the embedding shell knows — the builtin name set, shim visibility, the declarative registry — with the curated metadata tables in coreutils/pkg/atlas into one per-command record set for the `bashy commands` atlas views.
attest.go — function-call attestation (Sprint 216, B18): when a decorated Bash# function or an agentic{} function completes, ONE receipt is appended to the yoke skills/craft evidence ledger — the same skills.AttestRecord a `bashy skill run` writes, into the same <store>/attest/<name>.jsonl, read back by the same `bashy craft history`. No store of its own, no second record type, no model call.
Why the ledger and not a new log: pkg/craft was written as a READER over evidence that already existed, precisely to stop the eighth parallel outcome log. A function completing under a contract is evidence of exactly the kind a contracted skill run is — a named thing, at a coordinate, whose checks held or did not — so it belongs in the same place, where the read side already pools by name and coordinate.
What one receipt carries, in the record's own fields:
Name the function (a method is Type.Method) — the ledger file
Attest.Skill "fn:<name>", the identity that ran
Attest.Passed every clause check that held, as "<clause>:<check>"
Attest.Failed the check that failed, the same way
Attest.Valid the call completed (status 0) and no check failed
Status the call's exit status — 6 (skills.AttestYield) is an
agentic yield, recorded as a HANDOFF, never as a completion
ContextKey this host's environment coordinate (skills.HostCoordinate)
Tier the executor, "bashy@<version>" (the skills convention)
StoreRevision craft.Revision of the store, taken BEFORE the append
How one call becomes one receipt, whatever it is decorated with: every native decorator is wrapped by attestSink.attesting. The OUTERMOST native rung on a call opens an attestFrame on the ctx it hands down the chain; inner rungs on the SAME call (keyed on the engine's Call, so a decorated callee inside the body opens its own) find the frame and contribute verdicts to it; when the outermost rung returns, the frame is appended once. A clause under @retry re-records the same check per attempt and the last verdict wins. An agentic{} function with no source decorators is reached by an advised rung — newAdviceCallback adds the native `attest` decorator (a pure pass-through) to every agentic registration, so the frame opens with nothing to contribute but the completion itself.
A plain, undecorated, non-agentic function never enters a chain and never writes: identity. Bash OFF never registers decorators or consults advice, and --posix / cmd/bash never link this file: structural.
Off switch: BASHY_ATTEST=0|false|no|off. No store (an empty skills dir) is also off. An append that fails is spoken once on stderr — a ledger that silently stopped accruing would be an absence read as a clean record.
`bashy awd DIR -- CMD [ARGS…]` runs ONE command in another directory and comes back — the front-door form of the `awd` shell builtin (the in-process answer to `cd DIR && CMD` that never strands the shell in DIR). It exists so that no verb has to grow a `-C`/`-D` directory flag of its own: `bashy dag`, `bashy git`, a registered command, an applet — every one of them is "run it over there" through this single mechanism.
bashy awd ~/src/app -- bashy dag -f ~/pipelines/python.dag.md test bashy awd /tmp -- pwd
The builtin already works inside any bashy script or `-c` program; this file only routes the bare `bashy awd …` invocation to it (the same self-reexec shape as `bashy full`), so cwd, exit status, stdio and BASHY_* env behave exactly as they do for the builtin.
`bashy commands` lists the whole supported command surface in one place: shell builtins, the in-process coreutils userland, and the bare-name front-door verbs. The coreutils tools and verbs are dispatched by the ExecHandler before PATH, so they are otherwise invisible to `compgen`/`type` (which only see builtins, functions, and PATH) — this is the only way to discover them from inside the shell. Bashy-only (never the pure cmd/bash).
confirm.go — @confirm: effect-derived --what-if / --confirm on a Bash# function (Sprint 216, B16: PowerShell's SupportsShouldProcess borrowed without the shell).
A function declared `@confirm()` gains two call-site flags it does not implement itself. Both are GENERATED from the one effect source the surface already has — the Command Atlas effects a dispatched command declares (curated verbs, in-process tools, `bashy commands add` records) — and they are decided PER OPERATION, at the same ExecHandler rung that enforces the @guard cap, never once for the whole call:
- `fn --what-if ARG…` describes every governed operation the body would dispatch — `what-if: fn: would run <argv> (effects: …)` — and runs none of them. Pure/read commands run, so the description is exact.
- `fn ARG…` confirms each HIGH-IMPACT operation before it runs: one whose declared effects include destroy, spend, cred or priv, or one the atlas does not classify at all (fail closed, as Cap.Exceeded does). The question goes to the human through `bashy ask` — the channel the calling program does not own. `yes` runs it; anything else refuses it with status 126 and the body continues, as PowerShell continues after "No".
- When no channel reaches a human — bashy orchestrated the run (BASHY_AGENTIC), a first-party harness owns input (BASHY_ASK_HANDLER), or there is neither a usable terminal nor an attended askpass — the operation is NOT guessed. The call yields: exit 6 (weavecli.ExitInputRequired), one line naming the operation, its token and the resume form; nothing with a side effect runs after the yield point. See rfcs/0001-agentic-yield.md.
- `fn --confirm=TOKEN:yes,TOKEN:no ARG…` is the resume form: the answers a harness (or a human at a later terminal) supplies per operation. A token is derived from the operation's exact argv, so an answer applies to that operation and no other, whatever order the replay dispatches them in. Bare `--confirm` is accepted and means the default.
PowerShell mapping (pinned in confirm_test.go): pure/read = no ShouldProcess call; write/net/exec/remote/persist = ConfirmImpact Medium (auto-confirmed under the default High preference, but shown by -WhatIf); destroy/spend/ cred/priv = ConfirmImpact High (always confirmed). There is no $ConfirmPreference knob and no other common parameter: the gate set is a property of the effect vocabulary, not of a preference.
Only the FIRST argument is inspected, so a function's own flags are never stolen. @confirm is source-only (the advice loader knows no such decorator and the native refuses an advised call regardless). In plain Bash and POSIX mode decorator syntax does not parse and this file is inert; cmd/bash never links it.
contracts.go — the Bash++ contract decorators, @require and @ensure (Sprint 203, design by contract at the process boundary). They ride the same interp.Decorators slot as trace · guard · retry: a clause is one or more SHELL CHECKS (exit 0 = pass), the same contract `bashy dag`'s Require:/Ensure: lines follow, never an expression language and never anything generative.
- @require("<check>", ...) runs each check BEFORE the body; the first failing check ends the call with status 3 (weavecli.ExitPrecondFail) and `<fn>: precondition failed: <check>` on stderr. The body never runs.
- @ensure("<check>", ...) runs the body, then each check; a failing one sets status 3 and `<fn>: postcondition failed: <check>`. The postcondition judges a COMPLETED body only: a body that already failed keeps its own status, and in particular an agentic yield (exit 6, weavecli.ExitInputRequired) propagates untouched — a harness must never see "postcondition failed" masking "input required".
A check runs through Call.Run — in the decorated call's own frame: the callee's variables, its working directory, the call's arguments as $1..$n, and the shell's own exec-handler chain, so whatever governs the body (the @guard cap, registered commands, dry-run, auditing) governs the check the same way. @ensure additionally binds STATUS (the body's exit status) and RESULT (a typed function's first result value, else empty). A check's own assignments never leak back.
Both are source-only (D6): an advised @require/@ensure is refused here, as @retry is, until the tighten-only advice ratchet has its own gate.
dag_bashpp.go — the ```bsh dag body runs through the SAME runner wiring as `bashy --bashsharp FILE` (Sprint 216, Story 541).
yoke/pkg/dag ships its own Bash# interpreter for ```bsh bodies, and it has to: yoke may not import this package, so the runner it builds carries the coreutils userland and the target's effect cap and nothing else — no decorator registry, no policy advice, no attestation sink. That is exactly what run 36 hit: `@guard(effects: "read")` on an agentic function inside a dag body printed BASHPP-EDECO-UNDEF, the call failed with status 1, and the target still exited 0 because its body never looked at the status. A contract that is not registered is not a contract that fails — it is a contract that is silently not there.
The repair is wiring, not a second registry: re-register the bashpp tag with an interpreter that builds its runner through wireExec — the ONE seam every agentic runner in this binary goes through (main.go's cold CLI, the warm session, and now a dag body). Decorators (trace · guard · retry · require · ensure), registration-time advice, the attestation sink, the audit/advisor/learn middleware and the registered-command ring all come with it, in the order wireExec already fixes. dag's own CapExecHandler stays OUTERMOST, exactly where yoke places it, so a target's `Effects:` denial keeps its diagnostic and its exit 126 (B17) — the auditHandler inside enforces the same advice.Cap a second time, silently, which is what `@guard` relies on inside a function.
Classic (```bash and untagged) bodies are untouched: they carry no decorator syntax, and re-wiring them is not what the failure asked for.
decorators.go — the native Bash++ decorator set (trace · guard · retry, plus the contract clauses require · ensure in contracts.go) and the registration-time policy-advice wiring, injected into the shell through the existing wireExec seam (never --posix, never cmd/bash).
The engine (mvdan.cc/sh) exposes only the slots — interp.Decorators mirrors CommandResolver, interp.Advice fires at every function registration (declaration, eval, source, redefinition alike) — and every implementation lives here, riding machinery that already exists:
- @trace() → one span per decorated call through the OTel GLOBAL provider, the same library/binary boundary telemetry.ExecMiddleware draws (a library emits through the global provider; only the binary configures one). Call.Args/Results are NEVER attributes: typed cells can carry secrets, and the shape-over-content redaction that keeps an argv presentable cannot see into an arbitrary typed value.
- @guard(effects: "read,net") → an advice.Cap on the ctx handed to Next(). WithCap INTERSECTS with any outer cap (deny-only: a nested guard can never widen), and the shipped auditHandler — outermost in the ExecHandler chain, caps enforced even with a nil writer — refuses any command whose atlas effects exceed it, before the command runs, writing Decision "deny".
- @retry(n:, backoff:) → SOURCE-ONLY (never advisable; the rules loader refuses it and the native double-checks), a bounded, cancellation-aware loop over Next() on autoretry.Backoff's default schedule. No new retry engine.
Sprint 221, Story #580 (Story-ID c19cb824e6bd), B27b integration: the bashy half of `bashy define <name>` answering from a LIVE Bash# session.
`define` is one of alwaysShimVerbs, so a bare `define NAME` inside any bashy shell is really the installed function `define() { command SELF define "$@"; }` (see Preamble) — and `command` forces that through the normal external-command dispatch, i.e. the ExecHandler ring, with args = [SELF, "define", NAME]. Before that reaches the ring's actual exec and forks a whole second bashy process to ask the projected-registry lexicon, this rung answers a plain variable name straight from the session it is already running in, via interp.HandlerContext.DescribeValue (../sh, cad5a8e3) — the same engine seat @timed's parity work landed alongside (see ../sh/plan-b27-timed-introspection.md).
This is deliberately NOT an evaluator or a debugger: only a bare identifier is considered (defineSessionCandidate refuses flags, extra operands, and anything DescribeValue itself would refuse — a selector, an index, an expression), nothing is invoked or converted, and no cell is mutated. A miss — the name is not live in this session, or the call does not look like this exact shape at all — falls straight through to `next`, which is byte-identical to the fork-based front door `bashy define` has always used: a direct `bashy define NAME` (no running session at all, Dispatch's own case in agentos.go) never reaches this rung, and neither does any invocation this rung does not recognise.
`bashy doctor` is an environment self-diagnostic: it turns the footguns this shell documents (a wrapper shim shadowing `sh`, a stale `bashy` first on PATH, missing toolchain/engine, agent-mode state, bin cache) into a one-shot health table. Read-only and advisory; exits non-zero only when a check is a hard problem. Bashy-only (never the pure cmd/bash drop-in).
The agentic history recorder: every command bashy dispatches, and what each one taught about this machine's environment.
bashy's `history` builtin is bash-exact and therefore interactive-only — it records nothing on the script path, nothing on `-c`, and nothing on the ExecHandler path an agent drives. So the one process that sees every command an agent runs kept no usable record of it. This file is that record.
It writes two planes from one observation:
TIME pkg/execlog what ran, in what order, with what outcome
SPACE pkg/spacegraph what the environment IS — hosts, endpoints, accounts,
and the relations between them, accumulated over time
Three properties it must have, because it sits on the hot path of every command:
- It never changes the outcome. The underlying error is returned unchanged on every path, and a store failure is swallowed. A shell that failed a command because its note-taking broke would be indefensible.
- It costs one write(2) on an already-open fd. The measured median dispatched command completes in under a millisecond — these are the in-process coreutils calls that are bashy's whole structural advantage — so the audit log's flock+fsync model is not affordable here at any price.
- It is resolved ONCE. When the recorder is off, no middleware is appended to the chain at all, and nothing is opened until the first record.
inspect.go — `bashy inspect`: bashy's self-inspection verb.
The subject is bashy ITSELF. `doctor` answers about the host, `why` about the process tree, `otel` about a trace, `audit` about one store; inspect is the resource map (where every store bashy owns lives, scope-resolved for THIS cwd), the gate decisions (each middleware gate with the SIGNAL that decided it), and the index that names which verb answers every other question about bashy. It exists so an agent — or a third-party harness driving bashy — can navigate bashy without grepping its source.
Contract (docs in the umbrella; the public statement is `--help`):
- read-only, offline, model-free: no mutation, no provisioning, no network, no agent spawn;
- NEVER a second path computation: every row calls the accessor the owning package already uses. TestInspectNoPathLiterals fails this file on any `.bashy` / `.config/bashy` / `.agents/` literal;
- secrets: path and existence only, never a value or a size;
- an undeterminable row says "unknown" and why — never a default;
- deterministic for a given host state, so two hosts' `inspect paths --json` diff into a drift report.
`doctor`, `context` and `audit` fold behind it as aspects (subject = bashy AND effects = {read}); their bodies are unchanged and their old names remain as hidden aliases. A new read-only self-view is a new ASPECT here, never a new verb.
inspect_actions.go — `bashy inspect actions`: the catalog of what this bashy can RUN, one row per action, in the one vocabulary pkg/lexicon's action facet gives every runnable thing (a command, a skill, an agent binding).
An aspect of inspect, never a verb: it is a read-only self-view (subject = bashy, effects = {read}). And it is NOT a second projection: the rows are the facets of the SAME lexicon store `bashy define` answers from, listed rather than looked up — so the two cannot disagree about what running a name amounts to. The generic half only: identity, contract, latitude, authority, declared effects, executor. No path, no host, no Location — a facet row is shareable, a filesystem layout is not.
inspect_advice.go — `bashy inspect advice`: the advice (policy-applied decorator) configuration, one row per active rule.
An aspect of inspect, never a verb: a read-only self-view (subject = bashy, effects = {read}). And it is NOT a second policy parser: the decision comes from advice.FromEnv(os.Environ()) — the same loader the interpreter's advice wiring goes through — and the rows from Rules.All, the accessor the package publishes for exactly this surface. This file only renders what that call decided, and names the signal that decided it (the mode aspect's rule: the explanation is derived, the decision is not).
The off-states are answers, not absences: advice is opt-in (BASHY_ADVICE unset → off, the default) and ALWAYS off under VSC_PROFILE=cert — a cert run carries zero rules and never even opens the named file, so `inspect advice` under cert with a garbage BASHY_ADVICE still exits 0 with state=off. A configured file that fails to load is an error (exit 1), not a silent off.
The learning hook: every successful invocation teaches the host something.
bashy is the shell, so the ExecHandler already holds the fully-expanded argv — variables resolved, globs expanded, quotes handled, aliases applied — plus the exit code. There is nothing to parse and nothing to intercept; the only question is what to do with it.
What it does: on exit 0, extract the ENTITY the command targeted and the ROLES its arguments filled, and record them as facts. `ssh -p 2222 -l xuser remote-host` teaches that remote-host answers on 2222 as xuser — bound to the HOST rather than to ssh, so a later scp against the same machine can use it.
That entity-binding is the whole point. Knowledge recorded against a command only helps re-running that command; knowledge recorded against a host helps everything that targets it.
Command-not-found is the single largest class of agent shell failures (Terminal-Bench 2.0 taxonomy, 24.1%). The runtime already prints Bash's bare `command not found` with exit 127; an agent reading that has no idea whether the name is a typo, a tool that lives behind a door it can open, or genuinely absent — so it retries blindly.
This reactive ExecHandler middleware adds ONE structured hint on exactly that path: when a bare command name exits 127 AND is provably unresolved (not an in-process coreutil, not a registered command, not on PATH), it emits a single bashy-hint-v1 JSON line on stderr carrying the resolution tier, the nearest provided name (did-you-mean), and the install door if one exists (a GNU coreutils name is reachable through the managed container). It never alters the exit status or the Bash error text, is inert unless BASHY_AGENTIC is set, is silenced by BASHY_HINTS=off, and is rate-limited to once per name per session — exactly like the proactive nudger and the reactive advisor it sits beside.
Resolution is verified by lookup, never by the exit code alone — and in the SAME context the shell itself resolves: the default exec handler looks a bare name up with interp.LookPathDir(hc.Dir, hc.Env, name), so a PATH assigned inside the script or a relative PATH entry read after a `cd` resolves exactly as the command does. os/exec.LookPath against the process environment cannot see either, and would both (a) report a REAL command as not-found when it lives only on the shell-local PATH and (b) suppress the hint for a genuine shell-local miss that the host PATH happens to satisfy. The lookup is captured BEFORE the command runs, so a command that exists now and then removes itself (or rewrites PATH) and exits 127 keeps its genuine 127 rather than being reported as not-found.
The proactive half of the nudge subsystem (the advisor is the reactive half). When an agent uses a legacy tool that has a better agentic counterpart, bashy emits ONE rate-limited hint pointing at it — never changing the tool's behavior. First nudge: cd/pushd/popd → suggest `awd` (run one command elsewhere without leaking the shell's cwd).
Prime invariant: help, don't obstruct. Nudges are stderr-only (stdout stays pure data), rate-limited to once per (tool, session) so they teach without becoming noise, and fully silenceable. They are observers — they never block or alter the command.
`bashy release` is the distribution verb: what bytes leave this machine, and under what name. It is deliberately NOT an orchestration verb — every one of those (weave/sprint/dag/supervise) answers *who does the work and in what order*, and none of them owns an artifact's identity. The artifact names are a contract a third party consumes BY NAME (the fleet-upgrade receiver, binmgr), which is why the stages are subcommands of one verb rather than a new top-level family. See docs/plan-bashy-release-t0.md.
This is the T0 slice: build → archive → checksum, in-process, over the .goreleaser.yaml subset implemented by coreutils/pkg/release. It is LOCAL-FIRST — `--snapshot` needs no network, no credentials, and no tag. The unimplemented tail (sign/sbom/publish/announce) is refused BY NAME by the config loader, never silently skipped, so a run can never ship fewer assets than the config declares while still reporting success.
Why the whole GoReleaser CLI is not imported: measured at 77.3 MB / 277 new linked modules / 6 new MPL-2.0 deps (docs/bashy-release-pipeline-design.md §4), and the three stages T0 needs live in its internal/pipe/* and are not importable at any price.
`bashy run` wraps a single command and emits ONE structured record bundling the result with bashy's agentic meta — the generic alternative to per-tool --json output. The unique value is the META (non-lossy exit/signal, duration, cwd, and the space-time advisor's hints), not reformatting the command's output: agents read plain output fine, so by default the streams pass through live (tee) and only a compact meta line is added.
bashy run -- go test ./... # output live; trailing {…meta…} on stderr
bashy run --capture -- ssh host # no live output; one JSON record on stdout
# (embeds stdout/stderr) — for logging/transport
stdout stays pure in stream mode (the meta goes to stderr), so `bashy run` is pipeable and exit-transparent (it returns the command's own status).
R0-pre — register the agent session nobody else knows about.
`bashy agent list` is the address book, and its entries are `tool:model` bindings. Using it as an address book ASSUMES the singleton rule: one identity, one live occupant. That assumption is currently FALSE, and it was measured rather than suspected.
A session launched by `bashy chat` claims its identity through room.Join, so it appears in `chat sessions` and can be reached. A session launched the ordinary way — a human types `claude` in a terminal, with bashy configured as its shell — claims NOTHING. It is invisible to the room, invisible to `chat sessions`, and its identity looks free. On this host that produced two live `claude-opus5`: one registered at a pid the room knew, and one that had never been seen. A DM to that binding would reach the registered process, miss the other entirely, and report success.
bashy is the shell under every one of those tools, which makes it the only process positioned to notice. So it registers what it sees.
A presence card, NOT a claim ¶
The card is filed under a `shell:` id, deliberately outside the namespace room.Join claims for real sessions. That is the whole design:
- it can never refuse a later `bashy chat` launch of the same binding — a shell that locks the operator out of their own agent is a shell nobody runs, the same reasoning the weave guard follows;
- several raw launches of one tool coexist, each visible, because a human may legitimately run two;
- the address book groups by BINDING, so a collision becomes visible instead of silent, which is the entire point.
What it does not know, and does not guess ¶
The MODEL is not discoverable for a raw launch: DetectAgent reads env markers that name the TOOL (`CLAUDECODE=1` -> claude) and nothing carries the model. So Model is left EMPTY. A card claiming `claude:opus5` on the strength of a guess would put a fabricated address in the address book, which is worse than an incomplete one — the whole feature exists because a message went somewhere nobody was.
`bashy supervisord DAG.md TARGET` supervises exactly ONE `bashy dag` root in the foreground — the intentionally small init-shaped verb (Sprint 218).
What it does, and all it does:
- spawns the current executable as `bashy dag DAG.md TARGET`, in its own process group, with stdin/stdout/stderr inherited (no log files);
- on an UNEXPECTED exit (non-zero, or killed by a signal we did not send) restarts it with bounded exponential backoff, reset after a healthy run (Outpost's restart model; supervisord's `autorestart=unexpected`). An exit 0 is the root completing, and the supervisor exits 0 with it;
- on TERM/INT forwards the signal to the whole child process group, waits a bounded grace, then KILLs the group and exits 0;
- as PID 1 on Linux (or as a child subreaper, which it becomes when it can) reaps adopted orphans without racing the main child's wait — see supervisord_linux.go for the one race that matters and how it is closed.
The DAG's existing `Requires:` edges are the ONLY dependency format: the supervisor never reads the file itself. It asks the real runner for the plan (`bashy dag --json -n`) once at startup, so an unknown target or unparseable file is a diagnostic and an exit, never a restart loop, and the plan's order is printed so an operator sees what "the root" pulls in.
Deliberately NOT here (KISS, Sprint 218): daemonizing, pidfiles, a control socket, HTTP/RPC/UI, log files, config files, service scripts, watching the DAG file, multiple programs, readiness protocols, mounts, TTY setup, or anything a kernel boots. A host that wants those has outpost.
`bashy verify` codifies bashy's formal test batteries as first-class subcommands. The four are a PRECISION LADDER, not synonyms — the subcommand names keep the distinction visible so a claim is never overstated:
compat GNU Bash 5.3 COMPATIBILITY — behaves like the reference impl conformance mined POSIX CONFORMANCE — passes a standard's suite (measured) compliance official POSIX CERTIFICATION — granted by the authority (licensed) benchmark agentic BENCHMARK — performance/efficacy (not correctness)
Licensing posture (codified in suiteRegistry): the HARNESS is permissive and shipped; the TEST SUITES are never vendored. Public suites (GPL bash tests, yash) are fetched at runtime via an embedded URL into a gitignored cache — that is use, not redistribution, so no copyleft propagates to bashy. The official POSIX suite (Open Group VSC-PCTS) is LICENSED, not OSS: it is never auto-fetched; `compliance` is a stub that documents how to obtain a license and accepts a user-supplied local suite (`--suite PATH`).
These orchestrate the existing dev harnesses (Makefile targets + scripts/), so they run from a bashy source checkout — the natural home for conformance tooling.
The weave isolation guard: warn BEFORE the edit lands, not after.
weave detects that the live checkout moved while a run held its workspace, stamps `IsolationViolated` on the item, and makes `weave pull` refuse the merge. All correct, and all on the READ path — so whoever caused it finds out at the next `weave list`, often days later, when the only options left are --force or salvage.
This exists because it happened. An agent in the steward seat committed three files to the live bashy checkout while run #35 was in flight; nothing said a word at commit time, and the violation surfaced later in an unrelated `weave list`. Detection after the fact is a diagnosis; what was wanted is a warning.
bashy is the shell, so it is the ONE process that sees a mutating git command before it runs. That is the whole justification for the feature living here: no hook to install, nothing to remember, and it covers every agent CLI that routes its shell through bashy.
Sibling of coordHandler, and it borrows that file's hard-won lessons ¶
coord.go refuses a write when ANOTHER AGENT holds the project. This warns when A WEAVE RUN holds the checkout. Different source, different severity, same chokepoint — so it reuses coord's `isWrite` rather than growing a second argv matcher, and that reuse is load-bearing rather than tidy:
- `git` is a shell FUNCTION in every bashy session (the Preamble), so `git commit` arrives as `bashy git commit`. A matcher keyed on argv[0] == "git" catches /usr/bin/git and MISSES the only path an agent actually uses — the same silent no-op this file exists to eliminate.
- `git reset --hard` destroys work; a plain `git reset` only unstages. isWrite already draws that line.
It warns; coord refuses ¶
A weave violation is recoverable — `weave pull --force`, or salvage — and the operator often has good reason to edit the live checkout, including to fix what the run broke. Refusing would be wrong, and a guard that refuses gets switched off, at which point it protects nothing. One line to stderr, the command runs untouched, the exit code is never changed.
Index ¶
- Variables
- func Dispatch()
- func EnsureBash53Fixtures(root string) (string, error)
- func IsShellPresenceCard(c room.Card) bool
- func MarkOutputParent()
- func PosixDryRunNoExec(posix bool) bool
- func Preamble() string
- func PreambleFor(agentMode bool) string
- func ShellStartup()
- func Usage() string
- func WireExec(opts []interp.RunnerOption, posix bool, env []string, stdin io.Reader, ...) []interp.RunnerOption
- func WireSessionExec(initialDryRun bool) ...
- type PeerChannel
- func (c *PeerChannel) Close() error
- func (c *PeerChannel) DirectTCPIP(ctx context.Context, host string, port int) (net.Conn, error)
- func (c *PeerChannel) Exec(ctx context.Context, opts sshclient.ExecOptions) (*sshclient.ExecResult, error)
- func (c *PeerChannel) LocalForward(ctx context.Context, listener net.Listener, host string, port int) error
- func (c *PeerChannel) RemoteForward(ctx context.Context, remoteAddr, localTarget string) (net.Addr, error)
- func (c *PeerChannel) ReverseProxy(ctx context.Context, operatorURL, remoteAddr, noProxy string) (*PeerReverseProxy, error)
- func (c *PeerChannel) SFTP() (*sftp.Client, error)
- type PeerChannelConfig
- type PeerReverseProxy
- type PeerSSHServer
- type PeerSSHServerConfig
- type YAMLAgentSession
Constants ¶
This section is empty.
Variables ¶
var YAMLAgentPrepare func(string, []byte) (func() (YAMLAgentSession, error), error)
var YcodeHasExplicitConfig func(args []string) bool
YcodeHasExplicitConfig is wired beside YcodeMain by the embedding binary. It uses ycode's authored bootstrap parser to preserve flag value ownership.
var YcodeMain func(args []string) int
YcodeMain runs the ycode CLI (github.com/qiangli/ycode/pkg/ycodecli.Main) with its arguments and returns the exit status. cmd/bashy sets it: ycode imports bashy's pkg/harnessrunner, which imports this package, so this package cannot import ycode itself. Nil in a build that does not link ycode.
Functions ¶
func Dispatch ¶
func Dispatch()
Dispatch runs front-door commands through the observing portion of the ExecHandler chain. The terminal handler is still the existing dispatcher; the private exit signal only delays process exit until observers have seen the real status.
func EnsureBash53Fixtures ¶ added in v0.20.0
EnsureBash53Fixtures makes external/bash-5.3/tests present, fetching the GPL Bash 5.3 source tarball at runtime into a gitignored user-cache and symlinking it in — the embedded-URL pattern. Idempotent; a no-op when already present.
func IsShellPresenceCard ¶ added in v0.20.0
IsShellPresenceCard reports whether a card is a presence registration rather than a claimed session. Readers use it to render reach honestly: a presence card means "can send, cannot be pushed to".
func MarkOutputParent ¶ added in v0.32.0
func MarkOutputParent()
MarkOutputParent identifies an AgentOS shell process to its child bashy processes. Call it only after selecting the AgentOS route, never from package init: the same executable serves plain shell and coreutils applet aliases.
func PosixDryRunNoExec ¶ added in v0.20.0
PosixDryRunNoExec makes `bashy --dry-run --posix SCRIPT` a safe validation operation. AgentOS execution handlers are deliberately absent in POSIX mode; without this hook the dry-run flag would be silently ignored and the script would execute for real.
func PreambleFor ¶ added in v0.30.0
PreambleFor is Preamble for a session whose agent mode is known — a harness session is one (its request environment carries BASHY_AGENTIC=1) even when the process that renders the preamble is not.
func ShellStartup ¶ added in v0.30.0
func ShellStartup()
ShellStartup mints this shell's model-door session (Sprint 255 §3 scoping, Sprint 302 Q5): a child of the exported view it inherited, or a new root. Only the exported view goes into the environment, so every child process — in any language — sees the exported part only; gate executors start empty because the attest allowlist drops the variable.
func Usage ¶ added in v0.4.1
func Usage() string
Usage returns bashy-only help appended to the GNU-compatible shell usage.
func WireExec ¶
func WireExec(opts []interp.RunnerOption, posix bool, env []string, stdin io.Reader, stdout, stderr io.Writer) []interp.RunnerOption
WireExec appends the coreutils ExecHandler so any registered tool resolves in-process (pure-Go-first) before PATH lookup. It is wired into the shell core via cli.AgentOSWireExec. Shell builtins (echo, pwd, test, …) are handled by the interpreter before the ExecHandler runs, so they are never shadowed — only external-command names (ls, cat, grep, ast, …) are intercepted.
func WireSessionExec ¶ added in v0.22.0
func WireSessionExec(initialDryRun bool) func([]interp.RunnerOption, bool, []string, io.Reader, io.Writer, io.Writer) []interp.RunnerOption
WireSessionExec is WireExec with request-local initial dry-run state. It is the embedding seam: unlike WireExec, it never reads parsed CLI flags.
Types ¶
type PeerChannel ¶ added in v0.32.0
type PeerChannel struct {
// contains filtered or unexported fields
}
PeerChannel is an authenticated outpost SSH connection. It exposes only the operations the remote DAG lane needs: exec, SFTP and TCP forwarding.
func DialInstalledPeerChannel ¶ added in v0.32.0
func DialInstalledPeerChannel(ctx context.Context, host, user string) (*PeerChannel, error)
DialInstalledPeerChannel loads Bashy's persisted install identity and pinned host key for host. It fails closed when either file is absent or malformed.
func DialPeerChannel ¶ added in v0.32.0
func DialPeerChannel(ctx context.Context, cfg PeerChannelConfig) (*PeerChannel, error)
DialPeerChannel dials the remote's alternate outpost SSH port using the install-time private key. It never invokes system ssh or sshd.
func (*PeerChannel) Close ¶ added in v0.32.0
func (c *PeerChannel) Close() error
func (*PeerChannel) DirectTCPIP ¶ added in v0.32.0
func (*PeerChannel) Exec ¶ added in v0.32.0
func (c *PeerChannel) Exec(ctx context.Context, opts sshclient.ExecOptions) (*sshclient.ExecResult, error)
func (*PeerChannel) LocalForward ¶ added in v0.32.0
func (*PeerChannel) RemoteForward ¶ added in v0.32.0
func (c *PeerChannel) RemoteForward(ctx context.Context, remoteAddr, localTarget string) (net.Addr, error)
RemoteForward exposes outpost's loopback-only remote TCP forward. Cancel ctx to remove the remote listener and close its active connections.
func (*PeerChannel) ReverseProxy ¶ added in v0.32.0
func (c *PeerChannel) ReverseProxy(ctx context.Context, operatorURL, remoteAddr, noProxy string) (*PeerReverseProxy, error)
ReverseProxy forwards a remote loopback listener to an already running local bashy proxy. Proxy credentials, if present, are retained in the remote URL. Empty remoteAddr chooses an ephemeral IPv4 loopback port. NO_PROXY is explicit so the remote cannot silently inherit a bypass such as '*'.
type PeerChannelConfig ¶ added in v0.32.0
type PeerChannelConfig struct {
Address string
User string
PrivateKeyPath string
HostKeyCallback ssh.HostKeyCallback
}
PeerChannelConfig is the install-time identity needed to dial a remote bashy's outpost listener. HostKeyCallback is supplied by the caller's existing outpost trust store; Bashy neither invents nor weakens that model.
type PeerReverseProxy ¶ added in v0.32.0
type PeerReverseProxy struct {
// contains filtered or unexported fields
}
PeerReverseProxy owns a remote loopback listener backed by an operator proxy. It is scoped to ctx and must be closed when its commands have finished.
func (*PeerReverseProxy) Close ¶ added in v0.32.0
func (p *PeerReverseProxy) Close() error
func (*PeerReverseProxy) Exec ¶ added in v0.32.0
func (p *PeerReverseProxy) Exec(ctx context.Context, opts sshclient.ExecOptions) (*sshclient.ExecResult, error)
Exec exports both conventional cases before the literal command. SSH env requests depend on server AcceptEnv policy, so use quoted shell exports.
func (*PeerReverseProxy) URL ¶ added in v0.32.0
func (p *PeerReverseProxy) URL() string
type PeerSSHServer ¶ added in v0.32.0
type PeerSSHServer struct {
// contains filtered or unexported fields
}
PeerSSHServer owns an embedded outpost SSH server and its listener. Closing it only stops this user-space listener; it never touches system sshd or any other listener owned by the user.
func StartPeerSSHServer ¶ added in v0.32.0
func StartPeerSSHServer(ctx context.Context, cfg PeerSSHServerConfig) (*PeerSSHServer, error)
StartPeerSSHServer starts outpost's SSH server on the configured alternate port. The caller must provide install-time authorized keys and a persisted host key; no key is generated or silently trusted at runtime.
func (*PeerSSHServer) Addr ¶ added in v0.32.0
func (s *PeerSSHServer) Addr() net.Addr
Addr returns the actual listener address, including an OS-assigned port.
func (*PeerSSHServer) Close ¶ added in v0.32.0
func (s *PeerSSHServer) Close() error
Close stops the embedded server and is safe to call more than once.
func (*PeerSSHServer) Wait ¶ added in v0.32.0
func (s *PeerSSHServer) Wait() error
Wait waits for the embedded server to stop.
type PeerSSHServerConfig ¶ added in v0.32.0
PeerSSHServerConfig describes the user-space outpost SSH listener that a remote bashy starts for peer connections. HostKeyPath is the install-time host identity; AuthorizedKeysPath is the install-time client trust file. Address defaults to the LAN-reachable alternate port when empty.
Source Files
¶
- activity_verb.go
- advisor.go
- advisor_memory.go
- advisor_ram_linux.go
- advisor_unix.go
- agentic.go
- agentos.go
- agents.go
- atlas.go
- attest.go
- audit.go
- audit_verb.go
- awd.go
- bar.go
- budget_admission.go
- check.go
- check_null_safety.go
- check_prepare.go
- commands.go
- commands_sections.go
- confirm.go
- contain.go
- contain_container.go
- contain_image.go
- contain_linux.go
- context.go
- context_unix.go
- contracts.go
- coord.go
- dag_bashpp.go
- decorators.go
- decorators_std.go
- define_session.go
- dhnt.go
- doctor.go
- door.go
- dryrun.go
- engines_common.go
- engines_daemon_dir.go
- engines_podman.go
- engines_stub.go
- exechist.go
- exechist_diag.go
- forward_signals.go
- genie.go
- genie_builtin.go
- genie_external.go
- git.go
- git_verbs.go
- help.go
- inbox.go
- inbox_owner.go
- inbox_owner_linux.go
- inbox_poll.go
- inspect.go
- inspect_actions.go
- inspect_advice.go
- inspect_decorators.go
- installagent.go
- kbrecall.go
- learn.go
- limit.go
- mailbox.go
- manifest_rows.go
- mcp.go
- mcp_registered.go
- mcp_session.go
- mcp_verbs.go
- messageboard.go
- meta.go
- models_resources.go
- notfound.go
- nudge.go
- obs_stub.go
- output_reduce.go
- peer_channel.go
- peer_reverse_proxy.go
- podman_machine.go
- polyglot_agent_row.go
- polyglot_model_row.go
- polyglot_rows.go
- polyglot_tool_row.go
- proxy_env.go
- proxy_http.go
- proxy_socks.go
- refs.go
- registered.go
- release.go
- remote.go
- remote_exec.go
- resource_admission.go
- run.go
- run_unix.go
- self.go
- self_image.go
- self_pair.go
- self_seed.go
- shellsession.go
- sprint.go
- sprint_monitor.go
- sprint_monitor_alerts.go
- sprint_monitor_remote.go
- sprint_owner.go
- sprint_wait.go
- sprint_watch.go
- steward.go
- steward_mediator.go
- steward_meet.go
- steward_proc_unix.go
- steward_select.go
- steward_session.go
- steward_supervise.go
- supervisord.go
- supervisord_linux.go
- supervisord_unix.go
- text_fences.go
- toolchains.go
- transpile_paths.go
- verify.go
- weaveguard.go
- ycode.go
- ziglink.go
Directories
¶
| Path | Synopsis |
|---|---|
|
Package activity is the shared activity-event contract for Bashy subsystems: one compact envelope, one interest-routing matrix, and one delivery path onto the EXISTING durable notification and wake primitives.
|
Package activity is the shared activity-event contract for Bashy subsystems: one compact envelope, one interest-routing matrix, and one delivery path onto the EXISTING durable notification and wake primitives. |
|
Package session implements the warm `bashy serve` process: a persistent, local, explicitly-started listener that runs `bashy -c "…"` commands inside one already-initialized process, so an agent that fires thousands of shell invocations pays the process- and package-init tax once instead of per call.
|
Package session implements the warm `bashy serve` process: a persistent, local, explicitly-started listener that runs `bashy -c "…"` commands inside one already-initialized process, so an agent that fires thousands of shell invocations pays the process- and package-init tax once instead of per call. |