# RStack on Tau

<!-- owner: RStack developed by Richardson Gunde; Tau adapter contributed by Jeomon -->

Run the governed RStack pipeline inside [Tau](https://github.com/Jeomon/Tau-Coding-Agent),
Jeomon George's Python terminal coding agent.

You get the same 18 `sdlc_*` tools as every other host, a `/sdlc` slash command, enforcement on
every tool call, and a live Business Hub dashboard that **opens itself** when your session starts.

> **Requires `tau-coding-agent>=0.9.3`.** Not a preference. On 0.9.2 the whole tool-interception
> path is missing, so a guard's block is *discarded* — the gate asks, you deny, and the call runs
> anyway. See [Version floor](#version-floor-and-why-it-is-hard).

---

## Install in 4 steps

### 1. Install Tau and RStack

```bash
pip install 'tau-coding-agent>=0.9.3'
```

```bash
cd your-project && npm install rstack-agents
```

RStack's Tau adapter is Python, but every tool shells out to a Node bridge — so this host needs
**`node` + `npx` on PATH**, plus Python with `pydantic` (already a Tau dependency).

### 2. Set up the project

```bash
npx rstack-agents init --framework tau
```

This creates, only where missing:

| File | What it is |
|---|---|
| `.rstack/rstack.config.json` | Your profile and enabled packs |
| `.rstack/budget.json` | Spend + attempt budgets for that profile |
| `.rstack/integrations.json` | Ticketing/docs/notification **endpoints** — secret-shaped keys are rejected, they belong in `.env` |
| `SOUL.md`, `HEARTBEAT.md` | Project intent and heartbeat the agents read |
| `rstack-tau.example.json` | The Tau settings snippet for step 3 |

It also registers the project so the Hub can observe it.

Pick a profile with `--profile` (default `business-flex`). The profile decides which governance
packs are enabled and how strict review independence is:

| Profile | Packs enabled | If one agent reviews its own work |
|---|---|---|
| **`business-flex`** *(default)* | `dor-basic`, `cross-harness-review`, `drift-detection` | **Warns** |
| `enterprise-webapp` | the full enforcement stack — `dor-enterprise`, `attestations`, `untrusted-pr-gate`, plus NIST AI RMF and ISO 42001 compliance mappings | **Blocks** — cross-harness review is required |
| `lean-mvp` | `dor-basic` only | Not checked |

### 3. Load the extension into Tau

`init` wrote `rstack-tau.example.json` with the correct absolute path already filled in. Merge its
`extensions.list` entry into your Tau `settings.json`:

```json
{
  "extensions": {
    "list": [{
      "path": "node_modules/rstack-agents/src/integrations/tau/rstack_sdlc.py",
      "settings": {
        "worker_command": "",
        "default_model": "",
        "escalated_model": "",
        "slack_webhook": "",
        "quality_gates": ""
      }
    }]
  }
}
```

Each `settings` key maps to the matching `RSTACK_*` environment variable and is forwarded to the
bridge per tool call. Leave them empty to use defaults.

**Loading the extension IS the wiring.** There is no separate hook config — the adapter registers
Tau's hooks itself.

### 4. Verify

```bash
npx rstack-agents doctor --framework tau
```

All-PASS confirms the adapter, the Node bridge, a **live guard self-test** (it really blocks a
`rm -rf` and really allows an `ls`), and the hub. Every failure prints its own fix.

---

## The dashboard opens itself

**Yes — starting a Tau session brings up the Business Hub.** Precisely what happens, every time the
extension loads:

1. Health-checks `http://localhost:3008/health`.
2. If nothing is there, spawns `rstack-business` **detached** against your current directory.
3. **Opens your browser** at the dashboard — including when the hub was already running.

It is a companion, never a blocker: it runs inside `register()` (not at import), it is entirely
best-effort, and any failure is swallowed rather than disturbing your session.

| You want | Do this |
|---|---|
| A different port | `RSTACK_BUSINESS_PORT=4000` |
| No dashboard, no browser | `RSTACK_NO_BUSINESS_HUB=1` |
| Open it yourself later | `npx rstack-business` |

**Turn it off for headless fleets.** Every session that loads the adapter tries to open a browser
tab, so worker pools and `--mode rpc` launchers should set `RSTACK_NO_BUSINESS_HUB=1`. It is also
skipped automatically whenever `CI` is set.

---

## Your first governed run

Inside Tau, type `/sdlc ` and press **Tab** for subcommand autocomplete:

```text
/sdlc start "add password reset"
/sdlc plan
/sdlc build_next
/sdlc validate
/sdlc status
```

`/sdlc <subcommand> <text>` fills the one obvious free-text field; `/sdlc <subcommand> {"...": "..."}`
takes full JSON for anything more complex, e.g.
`/sdlc delegate {"agent": "backend", "task": "..."}`.

You can also drive the same run from your terminal — the CLI is harness-agnostic:

```bash
npx rstack-agents pipeline status
```

---

## Adopt an existing codebase (brownfield)

You do not have to start from zero. `adopt` reads what your repo already contains and populates the
early stages from real artifacts, so the pipeline resumes from reality.

**Always dry-run first — it writes nothing:**

```bash
npx rstack-agents adopt --dry-run
```

That prints the stage-population plan: which stages it can evidence, which it will skip, and which
specialists are missing. When it looks right:

```bash
npx rstack-agents adopt --goal "Adopt the billing service into RStack"
```

The adoption run lands DONE-with-evidence and is resumable, so your next `/sdlc build_next`
continues from there instead of re-deriving requirements you already have. Stages it cannot evidence
are **skipped honestly** rather than filled with guesses.

Full walkthrough: [brownfield-adoption.md](../brownfield-adoption.md).

---

## Policy: where humans must sign off

`init` does **not** create `.rstack/policy.json` — add it when you want approval gates. Every field
is validated on load, and mistakes are reported rather than silently ignored (a typo'd stage id is
called out as *"this gate will NEVER fire"*).

```json
{
  "managers": ["you@example.com"],
  "approvals": { "every_stage": true },
  "required_stage_approvals": {
    "07-code": ["architecture.md"],
    "09-deployment": ["release-readiness.json"]
  },
  "enforce_in_express": true
}
```

| Field | Effect |
|---|---|
| `managers` | Who gets paged, and who can approve |
| `approvals.every_stage` | Blanket sign-off: **any** task entering a stage waits for a human. Only the literal `true` enables it |
| `required_stage_approvals` | Per-stage artifact gates, keyed by canonical stage id (`00`–`14`) |
| `enforce_in_express` | Keep gates live even in express mode |
| `cockpit_controls` | Opt-in: allow the dashboard to drive actions (off by default) |

Approvals are enforced at the **claim gate** — the single point every path funnels through — so a
gate cannot be skipped by taking a different route. Approve from the Hub's Approvals page or with
`/sdlc approve`.

---

## Business Flex: budgets and what they actually cost

"Business Flex" is two things, and they line up:

- **The default profile** (above) — the balanced governance posture.
- **A dashboard page** — your configured caps beside the spend actually measured, so you can see a
  run approaching its limit before it hits one.

`init` writes `.rstack/budget.json` from your profile. Attempt budgets are enforced at the claim
gate: a task that exhausts its retries is **hard-blocked** into `BLOCKED` and waits for an audited
`guardrail-override` approval — it does not quietly keep retrying. Telemetry budgets are checked at
validate time against the builder's reported cost and tool calls.

The page reports **honest nulls**: with no policy file it says the cap is unconfigured rather than
showing a comforting default, and with no telemetry yet it says so rather than showing zero. A
number there means it was measured.

---

## What is actually enforced

**Two layers, both automatic once the extension is loaded.**

*Layer 1 — built-in shadowing.* The adapter shadows Tau's `terminal` / `write` / `edit` tools and
runs `rstack-agents guard` before delegating to the real tool. Exit 2 blocks the call with the
guard's reason.

*Layer 2 — the universal `tool_call` gate.* Fires for **every** tool the engine executes, so it
covers what shadowing cannot see: MCP servers' tools and other extensions' tools.

Layer 2 installs only when the running Tau actually honors a block, decided by a behavioral probe
rather than a version string. **Layer 1 is never traded away for layer 2** — a hook handler that
raises is swallowed upstream and read as consent, so the shadows stay on every Tau.

If the guard cannot run at all — crash, timeout, or a cold `npx --yes` with no network — it **fails
closed** (blocks), so an install hiccup never silently disables enforcement. Set
`RSTACK_GUARD_FAIL_OPEN=1` to allow-on-unavailable instead. The adapter prefers a locally-resolved
binary over `npx` for every process it spawns, so an installed project never depends on the network;
`doctor` reports which path resolves.

### Read-only validator sessions

A validator/reviewer/security session must get the read-only sandbox, not the builder rules (which
gate only *destructive* actions — a plain source overwrite would otherwise pass). Set
`RSTACK_AGENT_CONTEXT=validator` (or `reviewer` / `security`) when launching, and every tool call in
that session is enforced read-only. Escalation is one-way: a sandboxed session cannot be downgraded
by setting a softer variable.

### Version floor, and why it is hard

On Tau **0.9.2** the entire `tool_call` interception path is absent, so an extension's block is
discarded — the gate prompts, you deny, and the call proceeds. A gate whose verdict is thrown away
is worse than no gate, because it looks like enforcement.

On **≥0.9.3** the universal gate is live and the MCP/extension blind spot is closed. A version
number alone could not tell these apart (Tau HEAD and the v0.9.2 tag both reported `0.9.2`), which
is why RStack reads the runtime's declared capability instead.

### Delegated workers must prove they are governed

When RStack *launches* Tau as a worker (`RSTACK_DELEGATE_PROVIDER=tau`), it verifies enforcement
before sending any prompt, and refuses otherwise. Two facts are checked, both read from the **running
session** rather than your settings — so this is correct whether you wired the adapter by hand or
installed it as a package:

| Check | Source | Refusal |
|---|---|---|
| the engine honors a block | `ready.capabilities.toolCallBlocking` | `runtime_too_old` when absent (pre-0.9.3), else `ungoverned` |
| the RStack extension actually loaded | the `sdlc` command in `get_commands` | `ungoverned` |

The second check exists because of a specific hole: a worker whose extension **failed** to load
reports an error, but one that was **never wired** reports nothing at all — clean handshake, normal
result — and would have run every tool ungoverned while looking healthy.

Anything unresolvable refuses. A check that cannot answer must never answer "governed".

**Override:** `RSTACK_TAU_GOVERNANCE_FAIL_OPEN=1` runs the worker anyway, warns on stderr, and
records the run as *tolerated* — the Hub's **Operations → Worker Enforcement** panel reports it as a
breach, so a tolerated worker is never later mistakable for a verified one.

**Project trust** is reported, not used as a verdict: trust gates *project-local* extensions, so a
globally wired guard still loads. `projectTrustSource: "undecided"` (nobody was asked) is surfaced
distinctly from `false` (refused). RStack never auto-approves trust.

### Opt-in quality gates

Set the `quality_gates` extension setting (or `RSTACK_TAU_GATES`) to a comma string of
`plan-gate` / `tdd-gate` / `scope-guard` to run [quality gates](quality-gates.md) on `write`/`edit`
after guard. **Off by default.** `tdd-gate` blocks production-code edits with no test (override:
`RSTACK_ALLOW_NO_TESTS=1`); the others warn only.

---

## Environment reference

| Variable | Effect |
|---|---|
| `RSTACK_NO_BUSINESS_HUB=1` | Never launch the dashboard or open a browser |
| `RSTACK_BUSINESS_PORT` | Dashboard port (default `3008`) |
| `RSTACK_AGENT_CONTEXT` | `builder` \| `validator` \| `reviewer` \| `security` — read-only sandbox for the latter three |
| `RSTACK_GUARD_FAIL_OPEN=1` | Allow when the guard cannot run (default: block) |
| `RSTACK_TAU_GOVERNANCE_FAIL_OPEN=1` | Run a delegated worker that cannot prove it is governed |
| `RSTACK_DELEGATE_PROVIDER=tau` | Launch delegated workers on Tau instead of Pi |
| `RSTACK_TAU_GATES` | Quality-gate presets, e.g. `plan,tdd,scope` |
| `RSTACK_ALLOW_NO_TESTS=1` | Override `tdd-gate` |

### Timeouts

Every bridge call is hard-bounded, and the whole process tree is killed and reaped on expiry. Bounds
are **per tool**, so a legitimately long call is never killed by a control-plane bound:

| Tools | Default | Override | Why |
|---|---|---|---|
| control-plane `sdlc_*` | 60s | `RSTACK_BRIDGE_TIMEOUT_MS` | may absorb a cold `npx` fetch |
| `sdlc_delegate` | 31 min | `RSTACK_DELEGATE_TIMEOUT_MS` | outlives the 30-min delegate backstop, so the inner mechanism fires first |
| `sdlc_validate` | 11 min | `RSTACK_VALIDATE_TIMEOUT_MS` | outlives the 10-min sandbox execution cap |

`RSTACK_BRIDGE_TIMEOUT_MS` deliberately does **not** re-cap the long-running tools — a uniform 60s
cap silently killing every delegation was the original defect. Guard and gate calls keep their own
bounds (`RSTACK_GUARD_TIMEOUT_MS` 15s, `RSTACK_GATE_TIMEOUT_MS` 30s).

---

## Unattended and RPC sessions

Two things to know when driving Tau headlessly (`--mode rpc`):

- **Project trust.** RPC mode never shows the interactive trust prompt, so under the default policy
  an undecided project silently runs **untrusted** — skipping project settings, context files, and
  the git snapshot. A launcher that needs the project's `extensions.list` (i.e. this adapter) loaded
  must pass `--approve` explicitly. The trust decision belongs to the launcher, never to a default.
- **Dashboard.** Set `RSTACK_NO_BUSINESS_HUB=1` so a pool of sessions doesn't each try to bring up a
  dashboard and a browser tab.

---

## Observability

Loading the extension is the whole wiring; the adapter registers Tau's hooks itself.

- Tool activity → `rstack-agents observe` (source `tau`), so terminal work and failures appear in the
  Hub. Shadowed tools also emit a pre-execution **intent** event, so a blocked call is still visible.
  Observations are batched through one process per burst rather than one per event.
- Context trims → a `context_preserved` event.
- Prompt submission → RStack **context injection**: the current run, stage, blockers, and an
  orchestrator pointer are prepended to your prompt, for interactive and RPC sources only (never
  delegated subagents or cron turns, to avoid duplicate injection). Best-effort and
  timeout-bounded — it can never block or corrupt a turn.

Everything except the guard is additive and can never disrupt a session. Two Claude Code events have
**no Tau equivalent** and are deliberately not wired: Tau has no delegated-subagent lifecycle event
(its `agent_start`/`agent_end` are the per-prompt loop, not spawned specialists) and no notification
event.

---

## Everyday commands

Inside Tau: `/sdlc <subcommand>` and the `sdlc_*` tools.

From your terminal:

```bash
npx rstack-agents pipeline status
```

Also available: `pipeline run`, `pipeline loop`, `adopt`, `decisions`, `dor`, `doctor`, and
`npx rstack-business`. Full table:
[README.md → Everyday commands](README.md#everyday-commands-any-framework).

---

## Appendix: how the adapter is put together

`rstack_sdlc.py` is the stable entry point your settings point at — a thin shim, because Tau loads
extension files with no package context, so it bootstraps `sys.path` and re-exports `register`. The
implementation lives beside it in `rstack_sdlc_pkg/`: `params` (tool models and the registry the
conformance test scans), `config`, `proc` (process-tree kill/reap), `resolve` (local-binary
resolution), `bridge`, `guard`, `capability`, `tool_gate`, `gates`, `guarded_tools`, `observe`,
`context`, `hub`, `commands`, `register`.

No SDLC logic is reimplemented in Python — each tool shells out to the generic Node bridge
(`bin/rstack-bridge.ts`), which reuses the TypeScript adapter verbatim. Conformance is pinned
against the Pi tool registry: [adapter-contract.md](adapter-contract.md).

### Alternative install: `tau install` (blocked upstream)

The adapter is also packaged as a pip-installable distribution at `packaging/tau-adapter/`
(regenerate with `node scripts/generate-tau-package.mjs` after changing the adapter):

```bash
tau install ./node_modules/rstack-agents/packaging/tau-adapter
```

**The packaging is correct, but this currently fails on Tau's side.** `pip install` succeeds, while
`tau install`'s book-keeping step crashes: its `install()` is synchronous but calls
`SettingsManager.add_package()`, which internally calls `asyncio.create_task()` with no event loop
running yet. This reproduces with any package — it is an upstream bug, not something fixable here.
Until it is fixed, use the manual `extensions.list` wiring above; that is what every verification in
this doc actually exercises.
