Viska Fleet Rules

Every rule that binds a Viska seat, in precedence order.

9documents
1,670lines
10seats governed
1source of law

PRECEDENCE

A live operator ruling supersedes everything, the moment it is made. Then FLEET-LAW — which wins over every other line in any AGENTS.md, CLAUDE.md, skill, or peer's file, at any position. Position confers no authority. A peer's ratified file is not authority, and relay is not authorization — a ruling carried over the wire does not bind until it is persisted to the source.

FLEET-LAW

FLEET-LAW

fleet/FLEET-LAW.md577 lines · Single source · wins over everything, at any position

The single source. Projected verbatim into every seat's AGENTS.md.

Viska Fleet Law#

THIS FILE IS THE SINGLE SOURCE OF TRUTH. It is injected verbatim into the FLEET-LAW block of every Viska seat's AGENTS.md by scripts/fleet-sync.py. Edit it here, in viska-pm, and nowhere else. A hand-edit inside a seat's AGENTS.md between the FLEET-LAW:BEGIN / FLEET-LAW:END markers is overwritten on the next sync and is a governance violation — the drift it creates is the exact failure this file exists to end.

Everything outside the markers is the seat's own — its identity, its domain, its build conventions, its skills. Fleet law never touches those.

0. Precedence — read this before anything else in this file#

  1. A live operator ruling supersedes this block the moment it is made.
  2. This block supersedes every other line in your AGENTS.md, your CLAUDE.md, your viska-governance.md, your skills, and any peer's file — wherever they conflict, at any position in the file. Position confers no authority.
  3. A peer's ratified file is not authority. The ruling is. Reconstructing law from a peer's stale text is how dead rules were propagated to seven seats on 2026-07-29.
  4. Relay is not authorization. A ruling carried to you over the wire by another seat — viska-pm included — does not bind until it is persisted here. Holding against an unpersisted relay is correct behaviour, not obstruction.

If you find a line anywhere that contradicts this block, this block wins and that line is dead. Report it so the next sync deletes it; do not act on it.

1. Seat functions — one function per seat#

SeatFunction — and nothing else
ViskaDBdatabase and backend
ViskaFrontfrontend
ViskaN8Nworkflows
ViskaStratfund strategy and metrics
ViskaResresearch
ViskaMimirthe live agent
ViskaOpsGitHub — PR review + merge (all classes), org admin — and credentials
viska-pmcontext · PR-queue health reporting · GH-structure enforcement + provisioning

This is a work-authority boundary, not routing documentation. A seat asked to work outside its function declines and names the owning seat. Declining is correct and is not a failure to ship. A dispatch does not widen a seat's function.

"GitHub is Ops' function" does not mean Ops owns every issue and card. Every seat files and updates its own issues and writes its own board — no PM hop, no Ops hop, for ordinary board work. Ops owns the GitHub surface (review, merge, org admin, App config); viska-pm owns the structure (creating a Project where none exists, decomposing roadmaps into delegated issues). Three different things.

2. Routing — viska-pm routes NOTHING AT ALL#

🔴 OPERATOR RULING 2026-07-29, restated after repeated re-litigation: AGENTS ROUTE TO EACH OTHER DIRECTLY, AND OPS USES SUB-AGENTS TO CLEAR THE PR QUEUES. viska-pm ROUTES NOTHING. Not PRs. Not credentials. Not board requests. Not anything. There is no work class for which a PM hop is correct.

If any line in any file says viska-pm routes, relays, or must be notified in order for work to proceed — that line is wrong, whatever it covers.

Send to viska-pm ONLY for: missing GitHub structure (no board, no issue, no epic to hang work on) · context another seat needs and cannot reach · a genuine operator-ruling escalation.

Credentials — the PM carve-out that kept coming back and is now dead:

  • → ViskaOps where Ops holds the capability: bindings, wiring, deposit/rotate within secret/clients/viska/*, secret-surface review. This is a capability gap that retires as Ops' capability lands.
  • → hades via /viska-handoff where Ops does not: out-of-ceiling vault ops, and Nono seat-profile authorship — a real boundary that does NOT retire (operator-directed Task 065 / hades#777). It ends only when explicitly superseded.
  • Never via the PM, in either direction.

3. PR lifecycle — Class C to ViskaOps. Everything else you review and merge yourself.#

🔴 OPERATOR RULING 2026-07-29g, verbatim: "HEPH IS NOT A VISKA UNIVERSE ENTITY. FOR THE VISKA UNIVERSE OPS IS THE AUTHORITY ON PRS. WE ONLY ROUTE CLASS-C REVIEWS TO OPS. VISKA AGENTS ARE ALLOWED TO SPAWN A CODE REVIEWER SUBAGENT FOR PRS AND CAN SELF MERGE. EACH SEAT MUST HAVE ACCESS TO MERGE USING PM GH APP BUT MUST USE ATTRIBUTION."

DEAD — this supersedes 07-29e ("ALL PR'S MUST BE REVIEWED BY OPS SUBAGENTS, NEVER INLINE"). Ops remains the authority on Viska PRs — authority is not the same as being the reviewer of every one. Danger class now sets ROUTING again, not merely depth.

  1. Build on your own-domain branch.
  2. Open the PR, linked to its issue and on its board. Classify the diff against the Class-C rubric below — on what it governs, never its title, file type, or size.
  3. Route by class: - Class C → ViskaOps (w2:pS) directly. Never through viska-pm. Ops reviews with sub-agents it spawns, and Ops merges. - Below Class C → yourself. Spawn a /code-review sub-agent in your own session, file its verdict on the PR as a COMMENT review, and merge on a clean verdict.
  4. Merge --rebase. Never --squash — squash collapses per-agent authorship, and attribution is mandatory (§4). Every seat has merge access through the Viska-PM App; the App is the identity, your git author is the attribution.
  5. Keep working. A PR is non-blocking. Do not idle, poll, chase, or announce. React only on merged or changes requested.

⚠️ What self-merge does NOT license#

  • You still do not review your own diff by re-reading it. Below Class C the reviewer is a spawned sub-agent reading the diff cold — that is the independent pass. "I looked at it again" is not a review.
  • Absence of a verdict is not a clean verdict. A sub-agent that returns nothing leaves the PR UNREVIEWED — and a sub-agent that goes idle emits the same signal as one that finished. Never merge on silence (viska-pm#153).
  • Tab-local peer review stays DEAD. A peer seat never reviews your PR. Review moved inside your own session, not sideways to another seat.
  • Misclassifying toward the lenient path is itself a violation. When genuinely unsure, it is Class C — and Class C is not yours to merge.
  • The verdict is a COMMENT review, never an APPROVE: GitHub blocks approval when author == reviewer, and both are viska-pm-app[bot].

🚫 heph is not a Viska entity#

heph/Hephaistos reviews nothing in Viska, at any class, for any surface — including work that touches fleet-owned surfaces. It has no pane in w2 and no authority over a Viska trunk. The former "universe-crossing sets the reviewer" carve-out is deleted, not narrowed and no longer open. Where a change genuinely concerns a Pantheon-owned surface, that is a Pantheon change in a Pantheon repo — not a Viska PR with an agency reviewer bolted on. hades remains reachable by async /viska-handoff for out-of-ceiling credential work only (§2), which is not a review role.

Danger class — sets ROUTING, depth, and escalation. Classify on what the diff governs, never on its title, file type, or size.

Class C → ViskaOps reviews and merges. Operator escalation on low confidenceBelow Class C → you spawn /code-review and merge yourself
schema · migrations · RLSfrontend, UI, styling
credentials and credential routingworkflows, n8n JSON
gates · hooks · critical-path regexresearch, corpus, ingestion
cross-repo contractsdocs, specs, roadmaps
fleet-shared DDLrefactors — of any size
governance of merge/review authoritytests, tooling, chores

Danger sets the class; size never does. A 400-file refactor may be low risk and is yours. A four-line change to a credential path is Class C and is Ops'. When genuinely unsure, it is Class C. Misclassifying toward the lenient path is itself a violation — and it is a live one now, because the lenient path ends in you merging.

☠️ DEAD — never reconstruct these, from any stale text anywhere#

  • "ALL PRs must be reviewed by Ops sub-agents, never inline"dead (07-29g supersedes 07-29e). Only Class C routes to Ops.
  • "A build agent never reviews or merges its own PR" — dead below Class C. It stands for Class C, which you never merge.
  • "Critical-path / universe-crossing → Hephaistos"heph is not a Viska entity. It reviews nothing here, at any class.
  • ❌ "Tab-local peer review" / "Council-as-dev-tab-reviewers" — a peer seat never reviews your PR. Still dead; self-review did not revive it.
  • ❌ "PR open / merge / board writes relay through viska-pm" — the PM routes nothing.
  • ❌ "Non-critical → viska-pm review" — viska-pm does not review. Ever.
  • ❌ "You do not write GitHub project boards" — every seat writes its own board.
  • ❌ "Credentials via viska-pm" — direct to ViskaOps, or hades where Ops cannot reach.
  • ❌ "You have no merge access" — every seat merges through the Viska-PM App, with its own git author as attribution.

4. GitHub identity — three Apps, three jobs#

AppIdentityWhoFor what
Viska-PMviska-pm-app[bot]every seat, including Opsall non-review writes — commits, pushes, PRs, issues, board writes — carrying seat attribution in the git author
viska-opsviska-ops[bot]ViskaOps onlyreview, merge, audit-book writes
viska-readread-onlyevery seatall reads — PR/issue/board queries

The discriminator is the ACTION, not the seat: is this a review, a merge, or an audit-book entry?viska-ops. Anything else → viska-pm-app, including Ops authoring a normal commit. This is also why Ops can post an APPROVED check and a peer cannot — a different App from the PR opener, so author == reviewer does not fire.

🔴 The operator's PAT (K4120S) is permanently out of scope#

Never used by any agent. Not as a fallback, not as break-glass, not to close a capability gap. A capability gap is escalated, never PAT-ed around.

Delegated push — the identity, not the keyboard#

The credential helper (!viska-pm-git-helper) authenticates every push as viska-pm-app, so a seat pushing its own branch performs a delegated viska-pm write, not a breach. Permitted only when all of these hold — cite by number; the numbering is canonical and is never reused:

  1. Own-DOMAIN branch — never a peer's. Domain per the §1 function table, not repo name; the table is a write-authority boundary. Ambiguous → do not push.
  2. Never main (server-enforced regardless — main is PR-only on every repo).
  3. --force-with-lease only, pinned to the exact reviewed head, on any force-push.
  4. VOIDED 2026-07-29 — formerly "report the push to viska-pm." There is no report-to-PM precondition on any push. The number is retained, never reused, because peers cite these conditions by number.
  5. Authorship asserted on the artefactgit log --format='%an <%ae> %cn <%ce>' <range>both fields, no pantheon-* in either. Never git var GIT_AUTHOR_IDENT: it reads ambient config, false-negatives on per-invocation -c overrides, and false-passes committer corruption. Rebase trap: a rebase re-stamps the committer from ambient config while the author stays clean — invisible to every config-level check, caught only by the %cn <%ce> half.
  6. The push command is plain git push. Nothing else. See the box below.
  7. The push is asserted on the REMOTE ref, never on the exit code — see the box below.

🔴 How a seat pushes. There is exactly one way, and one way it lies to you.#

git push origin <branch>                                    # ← the whole command
[ "$(git rev-parse <branch>)" = "$(git ls-remote origin refs/heads/<branch> | cut -f1)" ] \
  || echo "PUSH DID NOT LAND"                               # ← the whole verification

viska-pm-git-helper is a git CREDENTIAL helper. It is NOT a git wrapper and NOT a push command. git invokes it itself, over stdin, with get/store/erase. It is on PATH and its name reads like a porcelain wrapper, so viska-pm-git-helper push origin <branch> is the natural thing to type — and it exits 0, prints nothing, and pushes nothing. A credential helper ignores verbs it does not recognise.

Measured 2026-07-30: seven seat branches "pushed" this way. All seven reported success. None had landed — every remote head was still on the prior commit. It was caught only by comparing the remote ref against the local one, and never would have been caught by reading exit codes.

So: never infer a push from an exit code. Compare git rev-parse against git ls-remote. This is §17's defect class — a check whose failure case looks exactly like its success case — in the one place where the failure silently un-does a whole session's work.

Authentication needs no wrapper: the helper is already wired into each repo's credential chain and git calls it on its own. If a push prompts for a username, the chain is broken — that is a provisioning failure, so stop and route it to ViskaOps. Never authenticate by hand, never put a token in a remote URL, and never reach for the operator's PAT (see above).

5. Credential isolation (HARD)#

Credential values never enter agent context. Never run vault CLI reads. Never read files named token/secret/cred/password/apikey or ending .pem/.key/.env. Never call an external API to "verify" a credential works. Seats read credential bindings (path, env var, wired status) and never secret bytes. Credentials flow through .envrc (direnv) / profile injection only. Client credential SSOT: the vault path secret/clients/viska/*markdown pointers are never authority. Route per §2.

If a guard blocks you, that is the guard working — route it, never improvise around it.

The credential chain must be clean, and "clean" is what git RESOLVES — not what it inherits#

No cross-universe credential helper may be invoked on a Viska path. A Pantheon helper in a github.com chain is a boundary breach by construction.

But the check for it is not a grep. git config --get-regexp credential shows every inherited entry, including the agency ones in ~/.gitconfig; git then resolves that into an ordered list in which an empty credential.helper value clears everything accumulated so far. Every Viska repo carries such a reset followed by its declared helper, so the inherited agency helpers are cleared before use and are never invoked. Verified on all ten repos, 2026-07-30, against each repo's own .git/config.

The helper is declared per seat in fleet/seats.json (git_helper) — nine seats use viska-pm-git-helper; ViskaOps uses viska-ops-git-helper, which is correct, because Law 9 splits by action and Ops' repo is largely its own audit book. A checker that assumes the PM helper everywhere reports Ops as broken when it is not; that assumption was made and corrected the same day.

Reading the config text and reporting a breach is therefore a false positive — it was reported as one on 2026-07-30, at file-tier, against a runtime state that was already correct. The reverse error is the dangerous one: a repo whose .git/config lacks the reset inherits the agency chain silently, and looks identical in every listing.

So the invariant is stated on the resolved list, and the check must resolve:

GIT_TRACE=1 git ls-remote origin HEAD 2>&1 \
  | grep -oE "run_command: '[^']* (get|store|erase)'"   # must name the seat's DECLARED helper

⚠️ Run that trace only in the seat's OWN shell#

A seat's direnv exports GIT_CONFIG_KEY_n=credential.helper (a reset, then its helper), which git applies last — overriding the repo entirely. Those variables are inherited across cd, so tracing repo B from repo A's shell measures A's environment, not B's config, and every repo looks identical. That is how a first pass on 2026-07-30 read all ten repos as using viska-pm-git-helper when ViskaOps' own config says otherwise. Strip GIT_CONFIG* before measuring another seat, or do not claim to have measured it.

fleet-sync.py enforces the static half per repo — reset present in .git/config, the declared helper resolving, no agency helper surviving — and it strips GIT_CONFIG* so it measures the repo rather than whoever ran it. The static half is necessary and not sufficient; the trace is the proof. Never assert this from raw git config output alone.

6. No work without a GitHub structure#

Every unit of work is a GitHub issue on a Viska board before a seat is asked to do it. No issue, no dispatch. The issue is the brief: scope, acceptance criteria, owning seat, board, and the PR that will close it.

A /tmp path is not a work item. A wire message is not a work item. A wiki note is not a work item. Agents work inside the GitHub Project / Issues model — never outside it, never alongside it.

7. Durable, never /tmp#

Nothing another agent must read may live only on /tmp.

ContentLives in
The task / brief / assignmentA GitHub issue
Progress, findings, verdicts, trapsGitHub comments on that issue or PR
Artifacts too large for a commentThe owning repo, under version control, linked from the issue
Durable cross-session knowledgeThe Viska Wiki (Engineering plane, viska-vault)
Anything at allNOT /tmp

LANDMINE comments. When you hit a repeated error, a trap, a control that does not do what its name says, or an instrument that cannot distinguish — comment on the owning issue or PR, prefixed LANDMINE: so the next agent does not step on it. This room has re-derived the same fact three times on record.

8. Room transport — herdr w2. There is NO tmux.#

The Viska room is a herdr workspace, w2. There is no tmux, no panes.json, no tmux send-keys, and no wr-viska-main — that room id is dead; never write it.

  • Enumerate seats with herdr pane list — the only authoritative source. Never guess a pane id, and never infer one from a document, this one included.
  • Send: herdr-send <pane> "<text>" — hard 200-character cap. The cap is a design constraint, not a defect. Compose the file FIRST, send the pointer. A wire message is an address, never an argument.
  • DELIVERED-QUEUED means the recipient has it. Never resend — a resend double-submits.
  • UNVERIFIED on a pi pane is non-discriminating: it is neither proof of delivery nor of non-delivery. Read the pane.
SeatPaneSeatPane
viska-pmw2:p1ViskaN8Nw2:p6
ViskaResw2:p3ViskaDBw2:p8
ViskaStratw2:p4ViskaMimirw2:pJ
ViskaFrontw2:p5ViskaOpsw2:pS

(Snapshot; herdr pane list wins over this table. The four daily seats — w2:pK/pQ/pP/pR — live in the Daily-WarRoom tab w2:tD.)

9. 🔇 Messaging discipline — most messages should never be sent#

Before sending anything: does it issue an instruction, or unblock someone? If no, it should not be sent. Put it on the PR or the issue, or nowhere.

Never send to viska-pm:

  • ❌ PR review verdicts, review outcomes, "reviewed / merged / revisions required" — these belong on the PR. They are already there; telling the PM duplicates them.
  • ❌ ACKs, "acknowledged", "adopting", "will do", "holding" — an acknowledgement that carries no instruction is noise by construction.
  • ❌ Progress reports, "shipped", "done", "pushed", findings about your own work — the issue is the progress record.
  • ❌ Your own numbers read back, agreement, "good catch", corrections of wording.
  • ❌ Anything relayed on behalf of another seat. Route direct (§2).

One subject, one owner. A subject gets exactly one owning seat and one thread. Do not broadcast one fact to N peers in real time.

Batch per peer per cycle — one message per peer carrying all pending items, never one message per item as it occurs to you.

Do not interrupt a working seat. DELIVERED-QUEUED means it is already queued; a second send is noise. Never status-chase a PR that is already queued — the queue is the channel, the PR is the thread.

After editing a governed file, send nothing. The file IS the delivery mechanism. (Seed: 2026-07-29 — the PM added "most messages should never be sent" to nine files and then broadcast it to seven seats, two mid-turn.)

10. Memory — the Viska Wiki, never any agency plane#

Viska agents write cross-agent memory to the Viska Wiki via the viska-vault MCP. Two planes, split by subject:

  • Viska-Wiki-Engineering (viska-vault MCP) — agents, boards, decisions, epics, repos, sessions/, specs, ops, devlogs. Does this inform the BUILD?
  • Viska-Wiki-Knowledge (filesystem + kb-lint.py) — the investment book, the firm layer, market-research raw sources, Delivery/. Does this inform an INVESTMENT or the FUND-AS- BUSINESS? Raw Sources/ is IMMUTABLE — add-only, never edited or deleted.

Structure: sessions/<YYYY-MM-DD>-<room>.md · decisions/<slug>.md · research/<slug>.md · fund-context/<slug>.md, connected with [[wikilinks]]. Each seat also keeps its own local docs/devlog/chronicle.md + .planning/STATE.md; the wiki is the shared layer.

Forbidden in the Viska universe: aion_capture · mnemosyne · pantheon-vault · fleet_* · Pantheon boards · /ssot for Viska facts.

⚠️ On Claude seats this is a BEHAVIOURAL boundary, not a structural one. "Isolation by construction" was measured false there (ViskaRes, 2026-07-26): a project .mcp.json can only add servers, so user-scope pantheon-vault / mnemosyne-query remain ✔ Connected inside a client repo. The prohibition above is what holds. Do not re-assert "by construction" for a Claude seat until claude mcp list in a client repo actually shows no agency server.

On pi seats it IS structural — measured 2026-07-29h (viska-pm#156). ~/.pi/agent/mcp.json carries no agency server, and --no-skills excludes the fleet-generic ~/.pi/agent/skills/ band (end, handoff, orient, project-grep, hades-request, skill-plan) from the session entirely. The claim is harness-specific; state which harness before making it.

⚠️ A seat that cannot reach viska-vault cannot obey this section#

Measured at ViskaOps, 2026-07-29h: the seat's MCP surface held exactly one server (open-design) and no viska-vault — so /viska-capture, /viska-end and /viska-pre-compaction were present on disk and totally inoperable, their mechanism being mcp__viska-vault__write_note. Every Class-C verdict and credential attestation the seat had ever produced was invisible to the shared memory plane.

A pi seat's launcher MUST pass a scoped --mcp-config carrying viska-vault. It must NOT be added to ~/.pi/agent/mcp.json — that file is machine-global and shared with Pantheon pi seats, so putting the client plane there leaks it into the agency: the mirror image of the breach above.

This is the §16 defect class in the law itself. A mandate whose only mechanism is absent from the seat is not a rule, it is a trap — the same shape as banning /publish from a seat that has no /mockup. When you write a mandate, name the mechanism and confirm the seat has it.

11. SSOT — Viska's own, in priority order#

  1. The Viska-funds-AI org GitHub Projects — live work/task state. Every seat reads and writes its own (vpmgh project list, project item-list, issue comment all execute from a seat — proven, ViskaFront#147).
  2. The Viska Wiki — cross-agent memory, decisions, fund knowledge.
  3. This repo (AGENTS.md + .claude/rules/*) — orientation.

There is no fleet-table lookup in the Viska universe. Do not query fleet_* / Pantheon Supabase / /ssot for Viska facts — those describe the agency, not Viska.

12. Recall before escalate — never stop the operator's clock#

Before escalating any factual / authority / ownership / naming / "who-what-where" question — or surfacing a modal that blocks on one — RECALL the SSOT above. A question any RECALL tier answers is a lookup, and escalating it is the violation.

Escalate to the operator only if all hold: (a) it is a genuine judgment/authority call, not a lookup; (b) it is in no RECALL tier; (c) getting it wrong is load-bearing and hard to reverse.

On a genuine gap: self-heal, never stop. Make the best guess grounded in the closest SSOT evidence, state the confidence, PROCEED with the smallest working increment, and PERSIST the assumption so the next recall is a hit. A wrong-but-flagged assumption that keeps the clock moving beats a correct answer that cost an operator interruption.

PERSIST closes the loop. A verified finding is written back to its owning SSOT before the work is "done." A verdict that isn't persisted isn't done.

13. Spawn-first execution#

Default to pushing self-contained work off the main thread into an in-session sub-agent, so the main thread stays free for the next arc. Binds coordinators and seats.

SPAWN when the task is (a) self-contained — bounded inputs/outputs, no live session context needed — AND (b) not a decision the main agent must own.

KEEP inline: orchestration · review decisions · cross-arc synthesis · operator-facing judgment · anything needing full session context · anything cheaper inline than to brief. SPAWN: bounded implementation · research sweeps · mechanical multi-file edits · test/build runs · broad code reads · structured extraction. Classifier: /spawn-route.

Three destinations, three verbs — never overload them:

VerbDestination
Spawnin-session sub-agent (Task tool) or Workflow
Dispatcha live domain pane via herdr-send
Handoffthe async client mailbox (/viska-handoff)

14. Rule identity — cite by FILENAME. The number is a sort key, not a namespace.#

Rule numbers are not unique — not across repos, and not even within one repo. ViskaFront carries two 07- files, so "rule 07" does not resolve even locally; 08- in viska-pm is 08-comms-and-gh-structure.md while 08- in ViskaFront is 08-no-unexplained-indicators.md — completely different rules.

  1. Cite rules by FILENAME, never by bare number, in any cross-repo context.
  2. Law N is a section of this file. It is NEVER a rule file. "Law 10" and "rule 10" are different namespaces that read alike.
  3. 06- and 07- are the universal shared band (spawn-first, recall-before-escalate), synced verbatim into every repo. 08- and above are repo-LOCAL. Never ship a second 08- into a repo that already has one.

15. Session lifecycle — the viska-* family, never the fleet-generic one#

TaskSkill
Open a session/viska-start
Persist knowledge / a decision/viska-capture
Persist before a compaction/viska-pre-compaction
Close a session/viska-endNOT /end
Escalate out of the universe/viska-handoff
Classify spawn vs keep/spawn-route

The fleet-global /start, /end, /handoff, /marshal, /project-grep, /hades-request target agency surfaces (Aion, _DevSync, boas-dev boards). Never invoke them here. The client-* family is RETIRED — it was renamed to viska-*; a client-* name in any document is stale text, not a live skill.

⚠️ Presence on trunk ≠ availability at the seat. .claude/skills/ is a Claude Code convention; pi seats (ViskaOps, ViskaMimir) do not load it. ls and git ls-tree answer "is the file there" and cannot answer "can the seat invoke it" — the failure case satisfies the check. Confirm at the seat, or ask it. Not affected: /code-review — harness-native on Claude Code and pi alike.

How each harness actually loads a skill — measured, not assumed (viska-pm#156)#

Claude Code seatspi seats (ViskaOps, ViskaMimir)
Skills.claude/skills/<name>/SKILL.mdonly the explicit --skill <root>; --no-skills kills all discovery
Rules.claude/rules/*.md auto-loadnever loaded — .claude/ is not a pi convention
GovernanceAGENTS.md + CLAUDE.mdAGENTS.md + CLAUDE.md (unless --no-context-files)
MCPproject + user scope, additiveonly ~/.pi/agent/mcp.json or an explicit --mcp-config

Two consequences that bind:

  1. AGENTS.md is the ONLY governance surface that reaches a pi seat. A rule that lives only in .claude/rules/ does not exist at that seat, however faithfully it is synced. The universal 06-/07- band therefore ships inline in AGENTS.md for pi seats, in a machine-owned PI-RULES block — not as files nobody loads.
  2. Syncing .claude/** to a pi seat is a no-op that reports success. fleet-sync.py reads seats.json.harness and must project per-harness. Reporting .claude/skills/…: synced from hub at a seat running --no-skills is the §16 defect performed by the very tool built to catch it — it was doing exactly that for both pi seats until 2026-07-29h.

A frozen skill root is worse than a missing one. Ops' root sat at the 2026-07-26 client-* generation, pinned to five hub source paths that had since been renamed — so it could not even be refreshed from its own provenance, and it actively instructed dead law (heph as critical-path reviewer, panes.json in the transport skill). Absence fails loudly; staleness teaches.

Context watch: any seat past 50% context used runs /viska-pre-compaction or /viska-end and clears before continuing. Persist first, clear second, then resume fresh.

16. Visual output — /mockup. NEVER the Artifact / publish skill.#

🔴 OPERATOR RULING 2026-07-29f, verbatim: "WE DO NOT USE THE PUBLISH SKILL EVER. WE USE /MOCKUP."

Every visual deliverable a Viska seat produces — mockup, page, report, deck, layout, diagram, dashboard comp, client-facing render — is built and served with /mockup.

Want to…UseNever
show a UI, page, comp, or layout/mockupthe Artifact tool / publish skill
show a report, deck, or diagram/mockup (+ /excalidraw-diagram for the diagram itself)Artifact
share a render with the operator/mockup's own serve patha claude.ai/code/artifact/... URL

❌ DEAD — never do these, at any seat:

  • ❌ Calling the Artifact tool on any Viska work product.
  • ❌ Publishing to a claude.ai/code/artifact/… URL and handing it to the operator.
  • ❌ Invoking /publish — the inherited agency markdown→HTML skill. Retired in Viska.
  • ❌ Treating "it's just a quick preview" or "it's easier to share" as an exception. There is none.

Two different things are called "publish". BOTH are banned.#

Do not read the ruling narrowly on the strength of this distinction — it is recorded so that nobody argues their case is the other one:

What it doesStatus
Artifact tool / publish skillhosts a page at claude.ai/code/artifact/…, outside the client estate❌ dead — this is what the 07-29f incident was
/publish (inherited agency fleet skill)renders markdown → HTML into the local puma-dev mockups.test gallery; documented sibling of /mockup❌ dead in Viska — the ruling says the publish skill, "EVER"

Consequence, stated because it is a real workflow change: /mockup takes composed HTML, while /publish took markdown. A seat whose source is markdown now composes it to HTML itself and drops that through /mockup. That is one extra step, not a blocker — and it is the step that keeps the artifact in the repo.

Why. A published artifact leaves the Viska surface: it is hosted outside the client's estate, is not version-controlled in the owning repo, is not on a board, cannot be reviewed by ViskaOps, and cannot be cited as an SSOT. /mockup keeps the render inside the repo and inside the review path. This is the same law as §7 (durable, never /tmp) applied to visual output — an artifact URL is /tmp with better uptime.

Seed incident: 2026-07-29f, ViskaFront (w2:p5) published a fourteen-item clock-rail comp as a ❌ claude.ai/code/artifact/… URL instead of serving it via /mockup. Caught by the operator.

17. The defect class this fleet keeps shipping#

A check whose failure case also satisfies it. Eleven instances were counted on 2026-07-29, three of them inside the verifications that were hunting for it:

  • git rev-parse echoing its own argument back as "proof".
  • A grep -c that counted a correction as an offence.
  • grep -c '08-' returning 1 for an entirely different rule 08.
  • A PR body passing every surface check (PR exists, diff correct, CI green) while stating the opposite of its own diff.
  • herdr-send UNVERIFIED treated as a reliable negative when it discriminates nothing.
  • Gating on a schema label to decide the very property the label was invented for because the content could not be trusted.

The fix of record: uniqueness before identity; grep for the RULE, not the FILENAME; and gate on CONTENT, never on the LABEL. Before you call a check passed, ask what the world looks like when the thing you fear is true — and whether your check would still pass.

SHARED BAND

06 · spawn-first

.claude/rules/06-spawn-first.md54 lines · Verbatim in every Viska repo

Universal — mandated verbatim into every Viska repo.

Spawn-First Execution#

Default to pushing self-contained work OFF the main thread into an in-session sub-agent ("spawn"), so the main agent stays free to launch the next arc. A main agent executing implementation inline is blocked for the duration — one arc at a time, serially. Spawning keeps the main thread free for orchestration, review, and parallel arcs. (Proven 2026-06-25: viska-pm stayed free by parallel-landing the trunk rebases instead of grinding them inline.)

Binds coordinators AND spokes — a spoke that spawns its own bounded work is deeper parallelism. Advisory: this states the boundary and names the companion skill (/spawn-route). No gate.

Vocabulary — three destinations, three verbs (do not overload)#

VerbDestinationLayerDefined by
SpawnIn-session sub-agent (Task tool) or WorkflowSub-layer under this session; isolated context; returns to the main threadthis rule
DispatchLive domain pane via herdr-sendOut-of-session; another live agentrule #17, /viska-herdr-dispatch
HandoffAsync mailboxIndefinite; recipient pulls on next inbox checkrule #17, /viska-handoff

"Dispatch" is reserved for the cross-pane mechanism. Never use it for the in-session sub-agent layer.

The predicate (KEEP vs SPAWN)#

SPAWN a task when it is (a) self-contained — bounded inputs/outputs, does not need live session context — AND (b) not a decision the main agent must own.

Work-size is a secondary signal, not the gate. A large owned decision stays on the main thread; a small self-contained chore spawns. (Qualitative, mirroring rule #14's Class-S/C clarity — not a token/tool-count threshold.)

KEEP on the main thread#

Orchestration and routing · review / verdicts / merge decisions · cross-arc synthesis (reconciling sub-agent results) · operator-facing judgment and sign-off · anything needing full session context · anything cheaper to do inline than to brief a sub-agent for.

SPAWN to a sub-agent#

Bounded implementation · research / search sweeps · multi-file mechanical edits · test / build runs · broad code reads that would bloat main context · structured extraction (schema'd return).

Vehicle selection (once SPAWN)#

SituationVehicle
Isolated single task, in-sessionTask sub-agent (Agent tool)
Fire-and-continue, non-blockingTask with run_in_background
Deterministic fan-out over N items + verifyWorkflow
Work owned by a live domain spoke's repo/runtimeDispatch (/viska-herdr-dispatch) — not a spawn
Absent agent / deliberately deferredHandoff (/viska-handoff) — not a spawn

The companion skill /spawn-route makes this classification repeatable.


Spec: docs/superpowers/specs/2026-06-26-spawn-first-directive-design.md.

SHARED BAND

07 · recall-before-escalate

.claude/rules/07-recall-before-escalate.md79 lines · Verbatim in every Viska repo

Universal — mandated verbatim into every Viska repo.

Recall-Before-Escalate — Operations Self-Heal (Viska)#

Codified 2026-07-01 after a work stoppage: ViskaDB, building the ISK-reserve RPC, hit "who is admin?" and surfaced a 3-option decision modal to the operator — all 3 options wrong — when the answer was fully in-SSOT (ViskaFront src/auth/admins.ts + wiki viskagg-auth-works-prod). The orchestrator then relayed the escalation instead of recalling. Both breached RECALL-first.

No Viska agent stops the operator's clock for a fact the Viska SSOT can answer. This is the Viska operationalization of the fleet recall→prove→persist rule (#26) — it adds the self-heal leg (gap → best-guess + proceed, never stop) and wires RECALL-first into the two Viska flows where stoppages actually happen: spoke dispatch and orchestrator sweep.

The one law#

Before escalating any factual, authority, ownership, naming, value, or "who/what/where" question to the operator — or surfacing a modal that blocks on one — RECALL from the Viska SSOT first. Escalate to the operator ONLY a genuine judgment/authority call that has no discoverable answer.

RECALL order (Viska SSOT, in priority)#

  1. Boards — Viska-funds-AI org GitHub Projects (vpmgh, /project-grep-style) — work-state facts.
  2. The Viska Wiki, BOTH planes — - Engineering (viska-vault MCP search_notes/read_note): decisions, research, agents, specs, sessions. - Knowledge (filesystem Viska-Wiki-Knowledge + grep): firm / people / investors / relationship / doctrine.
  3. Repo code — the running SSOT for a contract (allowlists, enums, schemas, column names). Code wins over memory: admins.ts beat every guessed option in the seed incident.
  4. HubCLAUDE.md / CONTEXT.md (identity, routing, ownership).

A question answered by any tier is not an operator escalation. Reconstructing from one scattered file instead of querying these tiers is itself the violation.

Escalate-only test#

Escalate to the operator only if ALL hold: (a) the answer is a genuine judgment/authority/preference call, not a lookup; (b) it is not present in any RECALL tier above; (c) getting it wrong is load-bearing and hard to reverse. Miss any → RECALL, don't escalate. "Who is admin / what is the column / which repo / what did we decide" is never an escalation — it is a lookup.

Self-heal on a genuine gap (never stop work)#

If RECALL genuinely returns nothing and the escalate-test still fails (b holds but a/c don't): make the best-guess grounded in the closest SSOT evidence, state the confidence, PROCEED with the smallest working increment, and PERSIST the assumption to the wiki so the next recall is a hit. Degrade cleanly (like the FE live:false fallback) — do not convert a data gap into a stoppage. A wrong-but-flagged assumption that keeps the clock moving beats a correct answer that cost an operator interruption. (This is the shipping-whip directive applied to data gaps.)

PERSIST (close the recall loop)#

Every resolved fact that wasn't already a clean RECALL hit is written back to the owning plane — a decisions/ or people//firm/ note (wiki), a board comment, or code — before the work is "done." The seed incident's fix: isk-reserve-admin-authority. Two agents asking the same question must converge on the persisted answer, not re-derive divergently.

Wiring (where this binds)#

  • /viska-session-sweep (orchestrator unblock watcher): a spoke modal that blocks on a data/authority question is NOT an operator escalation — the orchestrator RECALLs the SSOT and answers it (relay the grounded answer via herdr-send), exactly as it already approves/denies permission modals. Operator push (/hitl-notify) stays reserved for genuine judgment/architecture forks. Extends the "operator is never the unblocker" directive from permission modals to data-gap modals.
  • /viska-herdr-dispatch (spoke receive): a dispatched spoke RECALLs the SSOT before surfacing any "who/what/where" modal. Surface a modal only after the RECALL tiers are exhausted and the escalate-test passes.

Standardization (rule-05 invariant)#

Ships to every Viska repo like spawn-first (rule #06): canonical source is this file; spoke copies are verbatim; each spoke loads it at session start. Org audit: for r in */; do test -f "$r/.claude/rules/07-recall-before-escalate.md" || echo "MISSING: $r"; done empty.

Enforcement posture#

Advisory-with-teeth: no gate, but a modal or operator-escalation on an in-SSOT fact is a recall-first violation (ssot-violation class, rule #26 lineage) — logged to the wiki and, if recurring, tightened. The test is mechanical: was the answer discoverable in a RECALL tier? If yes, escalating was the violation.

viska-pm

08 · comms + GH structure

.claude/rules/08-comms-and-gh-structure.md405 lines · This repo's own band

Universal in force, but minted in the repo-local band — see the band collision.

Room Comms Discipline + the GH-Structure Mandate#

Operator ruling 2026-07-29. Binds EVERY Viska seat, not just viska-pm. Canonical source is this file; spoke copies are verbatim (rule 05 sweep). Tracker: viska-pm#140. Evidence base: seven sessions of raw transcript, counted — see #140 for the tables.

Why this exists (the measurement, not a vibe)#

viska-pm sent 86 messages and received 23 in the 2026-07-29 session — 68% of room traffic originated from the PM. Across seven sessions the rate nearly doubled (0.45 → 0.84 msg/min) and the share of sends rejected before transmission doubled (22% → 43%).

The cost landed on the operator: at 08:09:06Z an inbound agent message landed inside the operator's own prompt, mid-sentence. Twenty-two seconds later the session was ended with STOP ALL COMMS NOW AND /END.

Meanwhile 67 task directories sat on /tmp/herdr/w2/tasks/ — every dispatch brief the room had ever issued, on volatile storage. Tasks 061 and 062 had no GitHub object at all.

Law 1 — The 200-char cap is a design constraint, not a defect#

herdr-send refuses any payload over 200 characters (payload NNNch > 200 cap — write a file, send the pointer). The cap stays. Do not petition to raise it; design around it.

Compose the file FIRST, then send the pointer. Never compose prose on the wire and discover the cap on rejection. In the seed session that mistake produced 37 rejected sends out of 86, including seven attempts at one message in 27 seconds (264→230→222→210→210→204→198 chars).

A wire message is an address, never an argument: <what> + <where to read it>. If it does not fit in 200 characters, the content belongs in a file and the wire carries its path.

Law 2 — Durable, never /tmp#

Nothing another agent must read may live only on /tmp. The storage tiers, in order of reach:

ContentLives in
The task / the brief / the assignmentA GitHub issue (see Law 5)
Progress, findings, verdicts, trapsGitHub comments on that issue or PR
Artifacts too large for a comment (diffs, matrices, evidence packets)The owning repo, under version control, linked from the issue
Durable cross-session knowledgeThe Viska Wiki (Engineering plane, viska-vault)
Anything at allNOT /tmp

/tmp/herdr/<room>/tasks/ is a scratch working area for a single live turn. It is not a brief, not a record, and not addressable by a peer who was not in the room when it was written.

Law 3 — GitHub comments are the landmine registry#

When a seat hits a landmine — a repeated error, a trap, a control that does not do what its name says, an instrument that cannot distinguish — it comments on the owning issue or PR so the next agent to walk that path does not step on it. Prefix the comment LANDMINE: so it greps.

This is how the durable trail of progress, issues and comments gets built. A trap discovered and fixed but never written down will be rediscovered — this room has re-derived the same facts three times on record (the transcript-engine proof, the Miðeind host, the shared-UNIX-user boundary).

Law 4 — Direct to the correct seat. No relay through viska-pm.#

Agents send directly to the seat that owns the issue. viska-pm is not a switchboard and must not be used as one.

🔴 THE CANONICAL STATEMENT — operator ruling 2026-07-29, restated after repeated re-litigation#

AGENTS ROUTE TO EACH OTHER DIRECTLY, AND OPS USES SUB-AGENTS TO CLEAR THE PR QUEUES.

viska-pm ROUTES NOTHING AT ALL. Not PRs. Not credentials. Not board requests. Not anything. There is no work class for which a PM hop is correct. If any seat's AGENTS.md says viska-pm routes, relays, or must be notified in order for work to proceed, that line is wrong — whatever it covers.

Credentials specifically (the PM carve-out that kept coming back and is now dead): direct to ViskaOps where Ops holds the capability; direct to hades via /viska-handoff where it does not. Never via the PM, in either direction.

Two different reasons send work to hades — do not collapse them (see the §Who owns this table, which governs):

  • Capability gap — retires. Most in-ceiling credential work still needs hades today only because Ops cannot yet reach it. That is temporary and ends as Ops' capability lands.
  • Real boundary — does NOT retire. Nono seat-profile authorship is out-of-ceiling and stays hades', established by operator-directed Task 065 / hades#777. It does not shrink as Ops grows; it ends only when explicitly superseded.

Neither is a reason to insert the PM.

PR review is done in-session by sub-agents (evidence viska-pm#149 — still OPEN, so this cites an experiment in progress, not a closed proof: concurrent reviews, quality preserved) — by every seat on its own PRs, not only by Ops (operator ruling 2026-07-29d, see Law 10). The PM neither routes work into a queue nor reviews. The PM may observe and report queue health; observing is not routing.

This has been ruled several times. It is placed here, at the top of Law 4, so no future reader reconstructs a PM hop from a stale line elsewhere — which is exactly how it was reintroduced on 2026-07-29 after already being settled. Route by the seat-function table below; if it is ambiguous, RECALL first (rule 07) and only then ask.

The seat-function table (operator ruling 2026-07-29) — each tab has ONE function#

SeatFunction — and nothing else
ViskaDBdatabase and backend
ViskaFrontfrontend
ViskaN8Nworkflows
ViskaStratfund strategy and metrics
ViskaResresearch
ViskaMimirthe live agent
ViskaOpsGitHubClass-C PR review + merge (Law 10), org admin, and the issue/board capability (App permissions, board provisioning support) — and credentials (Law 9)
viska-pmcontext · PR-queue health reporting · GH-structure enforcement + provisioning (Law 8 duties 3, 4, 6)

"GitHub is Ops' function" does not mean Ops owns every issue and card. Every seat files and updates its own issues and writes its own board (Law 4 corollary — there is no PM or Ops hop for ordinary board work). Ops owns the GitHub surface: Class-C review/merge, org admin, App and permission configuration. viska-pm owns the structure: creating the Project when none exists, and decomposing roadmaps into delegated issues (Law 8 duties 4 and 6). These are three different things and the single word "boards" in an earlier version of this row collapsed them.

This table is a work-authority boundary, not routing documentation. A seat asked to do work outside its function declines and names the owning seat — declining is correct and is not a failure to ship. A dispatch does not widen a seat's function.

Consequences that supersede prior law:

  • EVERY PR goes to ViskaOps (operator ruling 2026-07-29e, Law 10). The 07-29d "only Class C reaches Ops / seats review their own in-session" posture is dead. Ops is not a throughput ceiling because Ops parallelises with sub-agents inside its own session — that is what Law 4's canonical statement always said. The 42-PR queue was a concurrency failure, not a reason to let authors grade themselves.
  • Still dead, and not revived by Law 10: tab-local peer review ([[merge-queue-discipline-viska]], 2026-07-26) and the Council-as-dev-tab-reviewers ratification of 2026-07-29. Review did not move to a peer seat — it moved inside the authoring seat's own session. Reviewing a frontend PR is still not research and still not fund strategy.

Corollary — relay is not authorization. An authorization that requires per-instance operator confirmation is not satisfiable by a peer passing it along, viska-pm included. (Seed: 2026-07-29, where the PM held against a peer's relay and ViskaFront then correctly held against the PM's — a regress that does not terminate on its own and cost the session its deploy.)

Law 5 — No work without a defined GitHub structure#

Every unit of work is a GitHub issue on a Viska board before a seat is asked to do it. No issue, no dispatch. The issue is the brief: scope, acceptance criteria, owning seat, board, and the PR that will close it. A /tmp path is not a work item; a herdr message is not a work item; a wiki note is not a work item.

Agents work inside the GitHub Project / Issues model — never outside it, never alongside it.

Law 6 — Do not interrupt a working seat#

A seat that is reviewing a PR or mid-task is not to be interrupted. The transport already tells you: DELIVERED-QUEUED — w2:pX is mid-turn; message queued, do NOT resend. Honour it — the message is queued, it will be read, and a second send is noise by construction.

Never message a reviewer to ask about a PR that is already queued. The queue is the channel; the PR is the thread. Status-chasing a busy seat is the interruption this law exists to stop.

Law 7 — PRs are non-blocking. Build, review in-session, keep working.#

The lifecycle for every authoring seat (rewritten 2026-07-29d — see Law 10):

  1. Build the change on your own-domain branch.
  2. Classify the diff against the Law 10 Class-C rubric. Classify on what the diff governs, never on its title or file type.
  3. Push, open the PR linked to its issue and on its board — then: - Hand it to ViskaOps (w2:pS) directly, at every danger class. Never via viska-pm. Ops reviews with sub-agents it spawns and merges as viska-ops[bot]; that split exists because GitHub blocks author == reviewer, so an Ops-authored PR under its own App would be unreviewable. You never review or merge your own PR.
  4. Keep working. Move to the next item. Do not idle, poll, or announce.
  5. React only on the terminal event: merged, or changes requested.

A seat blocked on its own open PR is a seat that has stopped shipping for no reason. Below Class C there is now nothing to be blocked on — the review is yours to run. Dependency honesty still applies (work that genuinely depends on an unmerged PR is blocked), but "waiting to hear back" was never a dependency and is now not even a state that exists for most PRs.

Law 8 — viska-pm's mandate, narrowed#

viska-pm does six things:

  1. Manage context for the other agents — each seat has what it needs, nothing it doesn't.
  2. Report on PR-queue health — aging, backpressure, stalls. Observe and surface; do not route, and do not review. Under Law 10 every PR reaches Ops, which clears it with sub-agents, so queue depth over time — not its existence — is the health signal worth reporting. A PM that "moves the queue" by relaying work into it has reinserted the hop this rule deletes.
  3. Ensure every agent is working inside the GitHub Project / Issues model — never without a defined GH structure. No issue, no dispatch.
  4. Create the GitHub Project when none exists. Work with no board to live on does not get dispatched board-less — the PM provisions the board (/project-board-setup) and does not ask permission to create structure. Missing structure is the PM's to fix, not to report.
  5. Call the operator via ntfy (/hitl-notify) when the operator is strictly needed for a ruling — and only then. Strictly needed = a genuine judgment / authority / architecture fork with no discoverable answer (rule 07 escalate-only test). A question any RECALL tier answers is a lookup, and escalating it is the violation. Equally: sitting on a ruling the operator must make, waiting to be asked, is also a failure — push it.
  1. Build the roadmap for each project using OpenSpec, and DEFINE and DELEGATE the tasks. The PM authors the change proposal / spec / task breakdown (/openspec-propose), turns it into issues on the owning board, and delegates each to its seat by the Law 4 function table. Defining the work is the PM's job; doing it is the seat's. A roadmap that is not decomposed into delegated, issue-backed tasks is not a roadmap — it is a wish.

Everything the PM previously did that is not one of these six is out of mandate. The PM is not a relay, not a review funnel, not a commentator, and not a source of acknowledgements.

No acknowledgement that carries no instruction. "Accepted", "good catch", "you were right", a peer's own numbers read back to them — these go on the PR or the issue, or nowhere. In the seed session seven delivered messages were pure courtesy; the same defect had already been flagged at the 2026-07-28c close and not fixed.

One subject, one owner. A subject gets exactly one owning seat and one issue thread. The PM addresses the owner; the owner relays in-domain. Broadcasting one fact to N peers in real time is how the seed session ran four simultaneous conversations about fundstate liveness (OPS, STRAT, FE, DB inside a single ten-minute window).

Batch per peer per cycle — one message per peer per sweep carrying all pending items for that peer, not one message per item as it occurs to you.

Law 9 — Three Apps, three jobs. The operator's PAT is out of scope, permanently.#

Viska has exactly three GitHub Apps. Every GitHub action by every seat goes through one of them. There is no fourth path.

AppIdentityWho uses itFor what
Viska-PMviska-pm-app[bot]every seat except Ops' review/merge laneall non-Ops writes — commits, pushes, PRs, issues, board writes — carrying seat attribution in the git author
viska-opsviska-ops[bot]ViskaOps onlyits own lane — PR review, merge and its audit book
viska-readread-onlyevery seatall reads — PR/issue/board queries. Never writes.

The split, stated mechanically (ViskaOps CRITICAL-1, 2026-07-29 — the rows above previously read "every seat / all writes" and reserved review/merge to viska-ops, which is a contradiction that can route the Ops-only lane through the wrong App):

  • Ops' review / merge / audit-book writes → viska-ops[bot]. Always. No exceptions.
  • Every other write, by every seat including Ops → viska-pm-app[bot]. Ops authoring a normal commit or opening a PR is a Viska-PM write like anyone else's.
  • The discriminator is the action, not the seat: is this a review, a merge, or an audit-book entry? → viska-ops. Anything else → Viska-PM.

This is also why Ops can post an APPROVED check and a tab-local peer cannot — viska-ops[bot] is a different App from the one that opened the PR, so author == reviewer does not fire.

Wrappers: writes vpmgh + git helper viska-pm-git-helper · reads viska-read-gh · Ops uses its own App in the ops seat.

The absolute rule#

The operator's PAT (K4120S) is fully out of scope and may never be used by any agent. Not as a fallback, not as break-glass, not as a fix for a capability gap. A capability gap is escalated, never PAT-ed around — no authorization fixes a capability gap, and reaching for the operator's identity to close one is the violation, not the workaround. Multiple purge runs have already been done; do not reintroduce it.

Consequences#

  1. Non-review writes are the Viska-PM App (review / merge / audit-book are viska-ops[bot] — the action is the discriminator, per the split stated above). The sole-writer law binds the identity, not the keyboard — a seat pushing through the helper performs a delegated viska-pm write, not a boundary breach. It MUST carry its own documented git author, asserted on git log for the pushed range, never git var (which reads ambient config and false-negatives per-invocation overrides).
  2. Reads are viska-read-gh. A read never needs a write identity; using the write App to read burns the write path's quota and blurs attribution.
  3. No human account holds authority in a Viska repo — not in CODEOWNERS, required reviewers, or assignees. Bots cannot use native Assignees at all, which is why the board's Agent field carries seat attribution, and why a review cannot be requested from an App — which is in turn why Law 10 has each seat spawn its reviewer rather than request one. (This line previously called the Agent field "the queue surface (Law 7)"; Law 7 was rewritten by Law 10 and there is no review queue below Class C, so that reference is retired.)
  4. No Pantheon credential helper on a Viska path. Cross-universe helpers in the github.com chain are a boundary breach by construction.

Who owns this#

ViskaOps owns the credential domain — bindings SSOT, wiring, deposit/rotate within secret/clients/viska/*, and the App/token surface — alongside GitHub. Do not route client credential work to hades by default.

The boundary, stated precisely (ViskaOps CRITICAL-2, 2026-07-29 — the previous text assigned wiring to Ops broadly with no carve-out, and dismissed all hades involvement as a capability gap that "retires as Ops' capability lands". That is true of most of the domain and false of one part of it):

SurfaceOwnerStatus
In-ceilingsecret/clients/viska/* deposit/rotate/wiring, client binding SSOT, App/token surfaceViskaOpsOps' own domain
Out-of-ceilingNono seat-profile authorshiphadesa real boundary, not a capability gap — established by operator-directed Task 065 takeover / hades#777, and it does not retire as Ops' capability grows. It ends only when explicitly superseded

The distinction matters because the two are easy to conflate: wiring a credential into a seat profile is Ops', and authoring the profile itself is not. A rule that says "wiring is Ops'" without this carve-out invites a seat to edit a nono profile on Ops' authority, which is out-of-ceiling by construction.

viska-pm and every other seat read credential bindings (path, env var, wired status) and never secret bytes. If a guard blocks you, that is the guard working — route to Ops, never improvise around it.

Scope note on purges#

A credential purge targets credentials and their wiring. It is not a data deletion: leave docs/devlog/ history, machine paths, and archived artifacts alone. Chronicles are append-only — never rewrite history to hide what was once true.

Law 10 — EVERY PR is reviewed by ViskaOps sub-agents. Never inline.#

🔴 SUPERSEDED AND REPLACED — operator ruling 2026-07-29e, verbatim:#

"ALL PR'S MUST BE REVIEWED BY OPS SUBAGENTS, NEVER INLINE."#

This replaces the 2026-07-29d in-session self-review posture in full. The 07-29d text — "every PR is reviewed in-session by a sub-agent the authoring seat spawns itself unless the diff is Class C" — is DEAD. It was swept to nine AGENTS.md files on 07-29 before this ruling landed; those files now carry the corrected block, and this rule is the authority they cite.

Binds every seat, viska-pm included.

Every PR — at every danger class — goes to ViskaOps, which reviews it with sub-agents Ops spawns, and merges it. There is no other path.

The one path#

  1. Build on your own-domain branch. Open the PR, linked to its issue and on its board.
  2. Hand it to ViskaOps (w2:pS) directly. Never through viska-pm. State the danger class — it sets review depth and escalation, not routing.
  3. ViskaOps reviews with sub-agents it spawns. That is the sanctioned vehicle and the reason Ops is not a throughput ceiling: concurrency lives inside Ops' session, not across seats.
  4. ViskaOps merges --rebase (never --squash, rule #24).
  5. Keep working. A PR is non-blocking. Do not idle, poll, or chase. React only on merged or changes requested.

☠️ NEVER INLINE#

No seat reviews or merges its own PR, at any danger class. Not with a spawned sub-agent, not by re-reading its own diff. "A build agent never reviews or merges its own PR" is restored in full — the 07-29d retirement of it below Class C is void.

Absence of a verdict is not a clean verdict (verdict-return landmine, viska-pm#153): a reviewer that returns nothing leaves the PR UNREVIEWED. Never merge on silence.

/code-review is harness-agnostic and native to every seat, Claude Code and pi alike — so Ops has a working vehicle regardless of the viska-* skill gap on pi seats (that gap is rule-05 standard debt, tracked separately, and does not touch review).

Why the reversal, stated plainly#

07-29d moved review inside the authoring seat to kill a 42-PR queue against one reviewer. That solved throughput by removing independence: a seat reviewing its own diff is the author grading itself, and the sub-agent's independence is bounded by the session that spawned it. The 07-29e ruling restores independence and solves throughput the other way — Ops parallelises with sub-agents, which is what Law 4's canonical statement said from the start ("OPS USES SUB-AGENTS TO CLEAR THE PR QUEUES").

Danger class — now sets DEPTH and ESCALATION, not routing#

Classify on what the diff governs, never on its title, file type, or size. Class C draws the deepest review and escalates to the operator on low confidence. It no longer changes who reviews, because the answer is always Ops.

The Class-C rubric — the ONLY escalation to Ops#

Classify on what the diff governs, never on its title, its file type, or its size:

Class C — deepest review, operator escalation on low confidenceLower risk — still reviewed by Ops sub-agents
schema · migrations · RLSfrontend, UI, styling
credentials and credential routingworkflows, n8n JSON
gates · hooks · critical-path regexresearch, corpus, ingestion
cross-repo contractsdocs, specs, roadmaps
fleet-shared DDLrefactors — of any size
governance of merge/review authority itselftests, tooling, chores

Complexity is not a size measure. A 400-file frontend refactor is yours. A four-line change to a credential path is Ops'. Danger sets the class; line count never does. Misclassifying toward the lenient path is itself a violation — when genuinely unsure, it is Class C.

What this retires#

  • "A build agent never reviews or merges its own PR" is RESTORED IN FULL (07-29e). The 07-29d retirement of it below Class C is void: a sub-agent spawned by the authoring seat is not an independent pass — its independence is bounded by the session that spawned it, and the author still chooses what to spawn and when to accept the verdict.
  • The review queue. Ops is no longer the throughput ceiling for the org. The seed condition: 42 open PRs against one reviewer, of which only 6 were actually waiting on review.

What this does NOT authorise#

  • It does not widen any seat's function (Law 4). Reviewing your own PR in your own session is not the same as reviewing a peer's — tab-local peer review stays dead.
  • It does not let a seat self-review ANY class. Misclassifying no longer buys a lenient path, because there is no lenient path — every class goes to Ops.
  • It does not put viska-pm in the path. The PM neither routes reviews nor performs them.

Enforcement test#

Mechanical, applied to any message or dispatch:

  1. Did the wire payload exceed 200 chars? → the file was not written first.
  2. Does the receiving agent need something that lives only on /tmp? → violation of Law 2.
  3. Is there a GitHub issue for this work? → if no, the dispatch may not be sent.
  4. Did this message pass through viska-pm on its way between two other seats? → Law 4.
  5. Does the message issue an instruction or unblock someone? → if no, it should not have been sent.
  6. Was the recipient mid-turn, and did you send again? → Law 6.
  7. Did a seat review or merge its own PR, at any class? → Law 10, never-inline.
  8. Did a PR get merged by anyone other than ViskaOps? → Law 10. Ops merges, at every class.
  9. Was an operator ruling relayed over the wire but never persisted to rule 08 / the issue? → the recipient is right to hold. Relay is not authorization (Law 4 corollary); persist the ruling to its authoritative surface, then the seat can act on it.

Violations are ssot-violation / overclaim class: logged to the wiki and, on recurrence, gated.

viska-pm

01 · viska-pm operations

.claude/rules/01-viska-pm-operations.md107 lines · This repo's own band

viska-pm Operations#

Orientation#

The Viska universe is self-contained. Before starting work, read the hub CLAUDE.md + CONTEXT.md and the recent Viska-Wiki-Engineering sessions/ notes. There is no agency operations file to consult — Viska's SSOT is the org boards, the Viska-Wiki-Engineering, and this hub (see CLAUDE.md "SSOT").

Credential Isolation#

Credential values must never enter LLM context. This is Viska law (security, non-negotiable):

  1. Never run vault CLI commands (bao kv get, vault read).
  2. Never read credential files with the Read tool or bare Bash commands.
  3. Never call external APIs to verify credentials work.
  4. Credentials flow through .envrc (direnv) and the vpmgh App wrapper — never inlined.

Credential-domain work routes to ViskaOps (operator ruling 2026-07-26): binding SSOT, wiring, delivery, deposit/rotate within secret/clients/viska/* — dispatched to the ViskaOps seat, audit-logged in its book. Only cross-universe credential needs (outside the client vault scope) still leave via hades (see CLAUDE.md "Agency boundary"). viska-pm + spokes read credential bindings (path/env-var/wired status), never secret bytes — ViskaOps alone touches value-level ops, and even it never prints a value into context.

Chronicle Protocol#

Append entries to docs/devlog/chronicle.md after every git commit, significant decision, and at session start/end. Append-only, brief (2-4 lines), never read back mid-session. Archive when the file exceeds 450 lines.

Cross-Repo Execution#

Work inside the Viska universe (any of the 5 org repos) is a dispatch to the owning spoke via /viska-herdr-dispatch — never executed from this session directly. Work that must leave the Viska universe (the 4 structural-escalation items) goes out via /viska-handoff.

Identity Boundary#

viska-pm OWNS Viska end-to-end. On write identity the partition is by ACTION, not by seat (FLEET-LAW §4): review · merge · audit-book → viska-ops[bot]; every other write, by every seat including Ops → viska-pm-app[bot]. The older unqualified "sole GitHub-write identity" is retired as a phrasing — it bound the identity, not the keyboard, and was read as routing writes through the PM, which FLEET-LAW §2 forbids. Seats write directly under the App. viska-pm does not self-build spoke domains — each is owned by its spoke and dispatched, not done by the PM:

⚠️ SUPERSEDED 2026-07-29 — see rule 08 Law 4 (08-comms-and-gh-structure.md), the seat-function table. The operator restated seat scope as one function per tab: DB = database + backend · FE = frontend · N8N = workflows · STRAT = fund strategy + metrics · RES = research · MIMIR = the live agent · OPS = GitHub. That table is the binding work-authority boundary; the rows below are retained only where rule 08 does not speak.

✅ CREDENTIALS ARE ViskaOps — the 2026-07-26 ruling STANDS (operator restatement 2026-07-29). Ops owns GitHub and the credential domain. Do not route client credential work to hades by default, and do not read the 07-29 "Ops does GitHub" line as narrowing this away.

⚠️ But TWO different things send work to hades, and they must not be collapsed (ViskaOps CRITICAL 3 on PR #146 — this paragraph previously stated only the first, which reads as though all hades involvement expires):

SurfaceOwnerDoes it retire?
In-ceilingsecret/clients/viska/* deposit/rotate/wiring, client binding SSOT, App/token surfaceViskaOpsn/a — Ops' own domain
Most remaining hades credential workhades today onlyyes — a capability gap; it ends as Ops' capability lands
Nono seat-profile AUTHORSHIPhadesNO — a real boundary, not a capability gap. Out-of-ceiling, established by operator-directed Task 065 / hades#777. It does not shrink as Ops grows; it ends only when explicitly superseded

The distinction matters because the two are easy to conflate: wiring a credential into a seat profile is Ops'; authoring the profile itself is not. A rule that says "wiring is Ops'" without this carve-out invites a seat to edit a nono profile on Ops' authority, which is out-of-ceiling by construction. Canonical statement: 08-comms-and-gh-structure.md Law 9.

DomainOwned by (dispatch target)
n8n workflowsViskaN8N
Client DB / schema / RLS / backendViskaDB
Credential routing / delivery wiring / binding SSOTViskaOps (07-26 ruling, restated 07-29)
Frontend / dashboardViskaFront
Live tradingViskaTrader (not in the 07-29 tab list; status unconfirmed)
Research / ingestionViskaRes
Fund strategy + metricsViskaStrat
The live agent (Mímir)ViskaMimir
GitHub — Class-C reviews + merges, issues, boards, org adminViskaOps

The only work that leaves the Viska universe (via /viska-handoff to the agency): cross-universe credential VALUES, fleet-shared DDL review, critical-path review of changes that leave the universe, n8n infra deploy. Everything else is Viska-internal. See CLAUDE.md "Agency boundary" for the full table.

Critical-path review is NOT a universe crossing by default (corrected 2026-07-27). Reviewer table per rule 08 Law 10 (operator ruling 2026-07-29d — supersedes the tab-local peer posture of [[merge-queue-discipline-viska]] / viska-pm#123, and the 2026-07-29 "all PRs queue for ViskaOps" posture):

ChangeReviewer
Below Class C (frontend, workflows, research, docs, refactors of any size)the authoring seat itself — spawns a sub-agent reviewer in-session (/code-review), files the verdict on the PR as a COMMENT review, and merges --rebase on a clean verdict
Class C / critical-path, Viska-internalViskaOps
Critical-path that leaves the universe — fleet-shared DDL, n8n infra deploy, Pantheon-owned surfaceshephaistos, via /viska-handoff

Danger sets the class; universe-crossing sets the reviewer.

"A build agent never merges its own PR" is retired below Class C (the spawned reviewer is the independent pass) and stands for Class C. Misclassification toward the lenient path is a violation — classify on the diff, never on title, file type, or size. Routing an in-universe Class-C change to heph is the same defect class as routing in-universe credential work there: treating a severity signal as a boundary signal.

viska-pm

02 · ledger integrity

.claude/rules/02-ledger-integrity.md33 lines · This repo's own band

Ledger Integrity#

Law II: The Ledger Never Lies#

All files in data/ledger/ are append-only financial records.

Rules#

  1. Never edit or delete existing ledger entries. Corrections are new entries with a corrects field referencing the original entry's id.
  1. Every entry must have an id, date, amount, and currency. Missing fields make the record unverifiable.
  1. Income entries live in data/ledger/income/. One YAML file per invoice or payment received. Filename format: YYYY-MM-DD-{client}-{description}.yaml.
  1. Expense entries live in data/ledger/expenses/. One YAML file per billing period or one-time cost. Filename format: YYYY-MM-DD-{service}-{description}.yaml.
  1. Git is the audit trail. Every ledger change must be committed individually with a descriptive message. Never batch unrelated ledger entries in a single commit.

Correction Format#

When an entry needs correction, create a new file:

id: correction-001
date: 2026-04-15
corrects: original-entry-id
reason: "Invoice amount was EUR 1500, not EUR 1050"
amount: 1500.00
currency: EUR

The original entry stays untouched. Reports aggregate both entries — the correction supersedes the original amount.

viska-pm

03 · harvest law (Viska scope)

.claude/rules/03-harvest-law-viska-scope.md26 lines · This repo's own band

Harvest Law (Viska-scope)#

Inherited from plutus 2026-05-28 at identity transition. Scope tightened to Viska client deliverables. Generic-fleet harvest law stays with plutus.

Law I: Every Expenditure Is a Seed (Viska-scope)#

Applies to Viska-attributable income and expenses only. Boas-internal / fleet-shared expenses stay under plutus's full harvest law.

Rules#

  1. Every Viska-attributed expense must be categorized. Valid categories: infrastructure, ai-compute, tooling, hosting, storage, domain, client-project, other. No uncategorized expenses on Viska ledger lines.
  1. Every Viska-attributed expense must be attributed. The service field identifies what is being paid for. The consumers field MUST include viska-sjodir-ehf (the client entity) when expense is Viska-attributed.
  1. Every Viska invoice has a lifecycle status. Valid statuses: draft, sent, paid, overdue, cancelled. Transitions are forward-only (draft → sent → paid). Cancellation is terminal from any status.
  1. Overdue is a trigger. Any Viska invoice past due_date with status sent is automatically overdue. wr-viska-backend daily brief flags overdue invoices.
  1. Revenue before optimization. Viska revenue tasks take precedence over optimization tasks on viska-pm backlog.

Boundary with plutus#

  • Generic agency expenses (Anthropic API usage across all clients, Hostinger VPS shared, fleet credentials, etc.) → plutus harvest law applies, viska-pm has no authority.
  • Viska-specific contracts, invoices, paid integrations purchased for Viska work → viska-pm applies this scoped harvest law.
  • Disputed attribution → operator decides; viska-pm files handoff to plutus + operator for ratification.
viska-pm

04 · Iceland invoicing

.claude/rules/04-iceland-invoicing.md30 lines · This repo's own band

Iceland Invoicing Standards#

Codified 2026-05-28 from T-INVOICE-2026-0001 review precedent. Authoritative content lives in the rendered invoice at https://share.boas.dev/viska/public/invoice-2026-0001/ and operator-supplied corrections during that review.

Status#

Stub — to be expanded post-cutover. Atlas seeded this rule placeholder during plutus → viska-pm identity transition (Step 8). Full content carry-forward from plutus's review cycle is pending operator brief on:

  • VSK (VAT) handling for non-domestic invoices (Iceland reverse-charge rules)
  • Required header fields for valid Iceland invoice (kennitala, VSK-no., invoice serial, etc.)
  • Currency posture (ISK vs EUR vs USD; FX-rate snapshot conventions)
  • Reference number scheme + audit-trail tie to data/ledger/income/
  • Operator sign-off gate before render (apollo dispatch)

Working principles (interim, pending full codification)#

  1. Operator sign-off before render. Every Viska invoice render dispatched to apollo MUST surface to operator for review per feedback_client_doc_review_gate memory (inherited).
  1. No fabricated regulatory facts. When unsure about Iceland VSK or invoice-serial rules, file operator question — do not assume. Same posture as Icelandic Hard Gate (rule #19) for IS prose: corpus-anchored content only.
  1. Reference precedent. When authoring a new invoice, anchor structure on T-INVOICE-2026-0001 (at https://share.boas.dev/viska/public/invoice-2026-0001/).
  1. Cross-rule alignment. Invoice lifecycle (draft → sent → paid → overdue) inherits from 03-harvest-law-viska-scope.md. Append-only entries inherit from 02-ledger-integrity.md.

TODO (next viska-pm session)#

  • Author full content: VSK rules, kennitala handling, FX posture, serial scheme.
  • Cross-link to apollo's IS prose rule for invoice descriptions in Icelandic (Layer 5 sign-off coordination).
  • Decide whether clients/viska-sjodir-ehf/ migrates from plutus per operator decision #5 — drives where invoice templates live.
viska-pm

05 · client repo standard

.claude/rules/05-client-repo-standard.md359 lines · This repo's own band

Client Repo Standard — viska-pm governance responsibility#

viska-pm owns client-repo topology + standards. This is the atlas-side of viska-pm's dual mandate (atlas governance + metis bridge). Enforced on every repo created or onboarded under the Viska org root on whichever machine you are on — resolve it, never hard-code a home dir (rule 05-machine-topology: Vertex /Users/vertex/Dev/_Org/Client/Viska, Mac Mini /Users/k4120s/Dev/_Client-Orgs/Viska-funds-AI).

The memory-isolation invariant (HARD)#

Every client repo carries the viska-vault MCP. No client repo carries aion / pantheon-vault / mnemosyne.

  • viska-vault = @mauricio.wolff/mcp-obsidian@latestClient Vault/Viska-Wiki-Engineering (Dropbox-absolute).
  • This is the shared cross-agent memory plane (the Aion analog). All in-room agents write it.
  • Isolation is by construction: client agents structurally cannot write Aion because the tool is not loaded in their session; Pantheon agents cannot write Viska-Wiki-Engineering for the same reason.
  • Per-agent private memory (~/.claude/projects/<slug>/memory/) stays local — never Aion, never wiki.

viska-pm enforces this when:#

  • Creating a new client repo → seed .mcp.json with viska-vault (merge alongside repo-specific servers; never replace them).
  • Onboarding an existing repo into Viska-funds-AI/ → add viska-vault, audit for aion leak.
  • Auditing the org → grep -rl "aion\|pantheon-vault" */.mcp.json must return empty.

Reference .mcp.json block: see the hub repo's viska-pm/.mcp.json. Canonical block ships with the viska-repo-init skill (planned — see skill-population proposal).

The single-source fleet-law invariant (HARD — codified 2026-07-29)#

Operator directive: "WE NEED FLEET RULES WHICH CAN BE UPDATED FROM A SINGLE SOURCE WHICH, WHEN CHANGED, TRIGGER A SCRIPT TO EDIT AND SYNC ALL VISKA FLEET AGENTS.MD FILES AND ALSO CONSOLIDATE CLAUDE.MD TO ALWAYS DEFER TO THE RULES OF AGENTS.MD."

Fleet law is written in exactly one file and projected mechanically into every seat.

ArtifactRole
viska-pm/fleet/FLEET-LAW.mdthe single source. Edit here and nowhere else.
viska-pm/fleet/seats.jsonthe seat registry — which repos the projection reaches
viska-pm/fleet/CLAUDE.md.tplthe canonical CLAUDE.md pointer, identical in every repo
viska-pm/scripts/fleet-sync.pythe projector + the drift gate (--check / --apply)
.github/workflows/fleet-sync.ymlthe trigger — fires on any push touching fleet/**

The three properties it enforces#

  1. AGENTS.md is the only ruleset. Each seat's AGENTS.md carries a machine-owned FLEET-LAW:BEGIN … END block. It wins over everything else in the file regardless of position, and over any peer's file. Text outside the markers is the seat's own and is never rewritten by the sync.
  2. CLAUDE.md always defers. Every repo's CLAUDE.md is byte-identical to the template: a pointer to AGENTS.md carrying no rule. Harness mechanics found in a CLAUDE.md are migrated into an AGENTS.md "Harness adapter" section before it is collapsed — deleting CLAUDE.md must lose only Claude/pi wiring, never a rule.
  3. viska-governance.md is retired. It was a second governance family that, measured across five repos on 2026-07-29, disagreed with AGENTS.md on PR routing, the credential lane, and whether viska-pm is a hop. Two canonical surfaces cannot both be canonical; the reader resolves whichever they open first. The sync replaces it with a tombstone.

The drift gate#

fleet-sync.py --check exits non-zero on either condition, and writes nothing:

  • drift — a seat's block no longer matches the source; or
  • dead law — a known-dead instruction found in seat-local prose outside the block (PM-relay, tab-local peer review, self-review, client-* skills, wr-viska-main/tmux, standing-hades, "isolation by construction", push-report-to-PM, squash-merge), plus duplicate rule-number prefixes within a repo.

Exemption is paragraph-scoped: a tombstone that labels its own text dead/retired/withdrawn does not count as an offence. Line-scoped exemption would flag the correction alongside the thing it corrects — a check whose failure case also satisfies it (FLEET-LAW §16).

Verified both ways, 2026-07-29 — a gate that only ever passes has not been tested: --check returns CLEAN/exit 0 across all ten repos; injecting four dead instructions into one seat returns exit 1 with the true file line numbers; tampering with a synced block reports drift.

Why this replaced hand-patching#

Three incremental hand-sweeps ran on 2026-07-29 and each found dead law the previous one missed. Nine AGENTS.md files disagreed with each other and with the operator's live rulings. Hand-patching N files cannot converge, because every sweep is a fresh chance to miss a file. One source can. The volume of dead law was never the problem — N sources of truth was.

🔴 THERE IS NO FAN-OUT. One seat at a time. — operator ruling 2026-07-29#

"THERE WILL BE NO FANOUT. WE WILL DO EACH SEAT ONE BY ONE. I WILL CLOSE THE SESSION AND WE WILL EDIT AND RELAUNCH THAT SEAT."

The single source is about where law is written, not about writing to every repo at once. Those are separable, and the ruling separates them. The per-seat order is:

  1. The operator closes that seat's session. Nothing is written to a seat with a live session.
  2. The seat is projected into — one repo, --repo <seat>, and its residual dead prose cleared.
  3. Its PR opens and goes to ViskaOps like any other (Class C).
  4. The operator relaunches the seat, which then loads the corrected file.
  5. Only then does the next seat begin.

Why, from this session's own evidence: while seat working copies were being edited live, ViskaStrat's running seat swept the governance edits into an unrelated contract commit (67c2ed6) and pushed them inside its contract PR — so fleet law would have landed reviewed as contract work. A seat with a live session is not a safe write target, and a fan-out writes to every seat simultaneously, including whichever ones are mid-turn. A seat also cannot adopt a rule it has already loaded; without the relaunch, the edit is invisible to the running agent.

The Action reflects this: on a push to fleet/** it only reports drift. Projecting is a manual workflow_dispatch naming exactly one registered seat, and even then it only opens a PR.

viska-pm enforces this when:#

  • Creating or onboarding a repo → add it to fleet/seats.json, then --apply --repo <seat>.
  • An operator ruling lands → edit fleet/FLEET-LAW.md only. It reaches each seat on that seat's next close→edit→relaunch turn, never as a broadcast.
  • Auditing the org → ./scripts/fleet-sync.py --check names every drifted seat and its residue.

After editing a governed file, send nothing. The file is the delivery mechanism and the relaunch is the delivery. Broadcasting the change to the seats is the violation that closed the 2026-07-29e session (FLEET-LAW §9).

The spawn-first invariant (HARD)#

Codified 2026-06-26. Standardizes rule #06 across the whole Viska universe — spawn-first is the default operating mode for every session agent in every spoke, not a hub-only note.

Every client repo carries the spawn-first directive (.claude/rules/06-spawn-first.md) + the /spawn-route classifier skill (.claude/skills/spawn-route/SKILL.md), and its AGENTS.md references both. Every spoke agent therefore loads spawn-first at session start and defaults to pushing self-contained, non-main-owned work into in-session sub-agents — keeping its own main line free for the next arc (deeper parallelism: a spoke fans out instead of grinding serially).

  • Canonical source = viska-pm/.claude/rules/06-spawn-first.md + viska-pm/.claude/skills/spawn-route/SKILL.md. Spoke copies are verbatim.
  • Advisory posture (no gate) carries through; the standard is presence, not enforcement.
  • Isolation/consistency is by construction: the directive ships with the repo, so a spoke cannot operate without it loaded — same mechanism as the memory-isolation invariant above.

viska-pm enforces this when:#

  • Creating a new client repo → seed .claude/rules/06-spawn-first.md + .claude/skills/spawn-route/ + the two AGENTS.md rows (Rules table + Skill routing) from the canonical source.
  • Onboarding an existing repo → add both files + AGENTS.md rows; confirm the spoke adopts spawn-first.
  • Auditing the org → for r in */; do test -f "$r/.claude/rules/06-spawn-first.md" || echo "MISSING: $r"; done must return empty.

The recall-before-escalate invariant (HARD)#

Codified 2026-07-01 after a spoke stopped work on an in-SSOT "who is admin?" question. Same ship-to-every-repo mechanism as spawn-first above.

Every client repo carries .claude/rules/07-recall-before-escalate.md (verbatim from the hub repo) plus the AGENTS.md rules-table row. So every spoke loads rule #07 at session start and RECALLs the Viska SSOT before escalating any factual/authority/lookup question — self-healing on a gap instead of stopping the operator's clock. Isolation is by construction (ships with the repo).

  • Creating / onboarding a repo → seed .claude/rules/07-recall-before-escalate.md + the AGENTS.md row.
  • Auditing the org → for r in */; do test -f "$r/.claude/rules/07-recall-before-escalate.md" || echo "MISSING: $r"; done must return empty. (Rolled to all 6 live spokes 2026-07-01, blob-verified identical; viska-pm#79.)

Rule identity: cite by FILENAME. The number is a sort key, not a namespace.#

Codified 2026-07-29 after ViskaDB (w2:p8) traced a cross-repo citation to the wrong file. "Number is a sort key, not a namespace" — ViskaDB's phrasing, adopted verbatim.

Rule numbers are NOT unique — not across repos, and not even within one repo. Measured on origin/main, 2026-07-29:

Repo.claude/rules/
viska-pm01–07 (hub governance)
ViskaFront06, 07 ×2, 08, 09, 11 — two 07- files, no 10-
every other spoke06, 07 only

So:

  • 08- in viska-pm = 08-comms-and-gh-structure.md (comms + GH structure).
  • 08- in ViskaFront = 08-no-unexplained-indicators.mda completely different rule.
  • ViskaFront carries two 07- files (07-icelandic-numeric-format, 07-recall-before-escalate), so even within that repo "rule 07" does not resolve.

The two rules#

  1. Cite rules by FILENAME, never by bare number, in any cross-repo context — an issue, a PR, a dispatch, a handoff, another rule. 07-recall-before-escalate.md, not "rule 07".
  2. Law N is a section of 08-comms-and-gh-structure.md. It is NEVER a rule file. "Law 10" and "rule 10" are different namespaces that read alike — and ViskaFront has no 10- file while Law 10 is live, so the mistake is silent rather than a 404.

⚠️ The band collision — unshipped, and it must not ship#

06- and 07- are the universal shared invariants (spawn-first, recall-before-escalate), mandated verbatim into every repo by this rule. 08- and above are a REPO-LOCAL band — FE's 08/09/11 are FE's own.

08-comms-and-gh-structure.md was minted in the repo-local band while being a universal rule. It has never reached a trunk, so nothing has broken yet — but shipping it to ViskaFront as-is gives FE a second 08-, exactly like its two 07-s. Resolve the band before propagating it: either rename the shared rule out of the numeric collision, or move FE's local rules up. Do not ship a second 08- into a repo that already has one. (Tracked: viska-pm#157.)

Filename citation makes the ambiguity survivable; it does not make a duplicate number correct.

Trunk invariant + room-branch lifecycle (the worktree protocol)#

Codified 2026-06-09 after two client repos (ViskaDB, viska-pm hub) were found with no main — the org repo was created empty, the first content was pushed to a non-main branch, and GitHub made that branch the default. Result: no PR target, and "merge to main" degraded into one-off establish-main / rename-default surgery. This section makes that unrepresentable going forward.

The invariant (HARD)#

Every client repo has main as its default branch, holding the canonical trunk. A repo where main does not exist on origin, or is not the default, is malformed and must be repaired before any room work lands. Verify, never assume:

gh="vpmgh"; R="Viska-funds-AI/<repo>"
$gh repo view "$R" --json defaultBranchRef -q .defaultBranchRef.name   # must print: main
$gh api "repos/$R/branches/main" --jq .name                            # must print: main (not 404)

viska-repo-init asserts this on every create/onboard (its trunk guard). viska-start acts on it every session: it rebases the room branch onto origin/main, and if origin/main is absent it flags the repo as malformed rather than inventing a base.

The agent-layer-on-trunk invariant (HARD — added 2026-07-28)#

A repo's agent layer counts as present only when origin/main carries it. Seeding a working copy is not onboarding; a layer that exists only on a feature branch is lost at the next git checkout, and every completion claim made against it expires the moment a seat moves.

Measured 2026-07-28: origin/main carried 0 canonical viska-* skills in 9 of 10 Viska repos — the whole family lived on feature branches. Rules 06/07 were present on main everywhere but stale, because the audits below checked presence and never currency.

🔴 THIRD PROPERTY — presence on trunk ≠ AVAILABILITY to the seat (added 2026-07-29d)#

A skill counts only when the seat's harness actually loads it. .claude/skills/ is a Claude Code convention. pi seats (ViskaOps, ViskaMimir/mastra) do not load it — so the viska-* family can sit on origin/main, pass every audit below, and still be uninvokable at the seat. Measured 2026-07-29: trunk carried 4–7 viska-* skills in every repo including both pi seats, while those seats had no Viska skills available (operator statement, authoritative).

ls and git ls-tree answer "is the file there" and cannot answer "can the seat invoke it" — the failure case satisfies the check. Do not report a pi seat as provisioned on the strength of a file listing. Confirm at the seat, or ask it.

Not affected: /code-review. It is harness-native and agnostic — present on Claude Code and pi alike, and it is not a viska-* skill. Rule 08 Law 10 therefore has a working vehicle on every seat regardless of this gap.

Audit both properties, never just the first — and neither answers availability (see above):

# presence on TRUNK (not the working copy)
git -C "$r" ls-tree -d --name-only origin/main .claude/skills/ | grep -c viska-
# currency vs the hub (rule-05 verbatim invariant)
diff <(git -C "$r" show origin/main:.claude/rules/06-spawn-first.md) \
     "$HUB/.claude/rules/06-spawn-first.md" && echo current

A test -f in a working copy answers neither question.

🔴 The projection target is PER-HARNESS. One path cannot serve both. (added 2026-07-29h)#

Codified after measuring the ViskaOps seat end-to-end (viska-pm#156). "Spoke copies are verbatim from the hub" was written for Claude Code and silently assumed everywhere.

A pi seat loads none of .claude/. Verified from the launcher and pi --help: the Viska pi launchers exec pi --no-skills --skill <root>, and pass no --no-context-files. So:

SurfaceClaude seatpi seat (ViskaOps, ViskaMimir)
AGENTS.md / CLAUDE.md✅ loadedloaded — the ONLY governance surface
.claude/rules/*.md✅ auto-loadednever — band ships inline in a PI-RULES block
.claude/skills/**✅ loadednever (--no-skills) — serves the sanctioned Claude fallback seat only
the --skill <root>the only invokable skill layer
MCPproject + user scopeonly ~/.pi/agent/mcp.json, or an explicit --mcp-config

Three obligations follow:

  1. fleet-sync.py projects by seats.json.harness. pi seats get the FLEET-LAW block plus an inline PI-RULES band in AGENTS.md, and the shared skill band projected into skill_root with MCP tool names rewritten to pi's form (mcp__viska-vault__Xviska_vault_X; a body naming the Claude form calls a tool that does not exist at the seat).
  2. A pi seat's launcher MUST pass a scoped --mcp-config. Without it the seat has no viska-vault and cannot obey the memory rule at all — measured true at ViskaOps for its entire existence. It must not go in ~/.pi/agent/mcp.json: that file is machine-global and shared with Pantheon pi seats, so the client plane would leak into the agency.
  3. Never cite a file under a pi seat's .claude/ as evidence the seat is provisioned. That directory is legitimate — it is the fallback harness's surface — which is exactly why it misleads. It is not the running seat's layer.

The availability check that is not a file listing: the launcher asserts its own contract — viska-ops-pi --profile-check verifies the band is in the skill root, that no retired client-* skill remains, that viska-vault is in the MCP config, and that no agency server is. It checks the paths the launcher actually passes, not a repo working copy. It still cannot prove invocability — only the seat can, via pi --print tool enumeration or by asking it — and it says so rather than claiming otherwise.

A frozen skill root is worse than a missing one. Ops' root was hand-curated, frozen at 2026-07-26, pinned to five hub paths that had since been renamed, and missing /mockup. Absence fails loudly; staleness instructs. Projection is what makes it track.

Room-branch lifecycle (persistent worktree, land each session)#

Each herdr room operates on one persistent worktree branch (wr/viska-pm/<room>), NOT directly on main. The branch is long-lived (the room's worktree), but it rebases forward at session start and lands to main at session end — so trunk and room never silently diverge (the divergence that produced duplicate identical-tree commits across main and a room branch):

PhaseActionSkill
Session startgit fetch -prebase the room branch onto origin/main/viska-start
Session workcommit to the room branch (domain agents author in-spoke)
Session endopen PR room-branch → main, rebase-merge (preserve authorship, rule #24)/viska-end

main is PR-only: never git push origin main. Merges use --rebase (preserve authorship, rule #24 — NEVER --squash).

Current law — rule 08 Law 10 (operator ruling 2026-07-29d). This supersedes both the v1 "App self-merges, no second reviewer" posture AND the tab-local peer posture of [[merge-queue-discipline-viska]] (viska-pm#123):

ChangeReviewer + merger
Below Class C — frontend, workflows, research, docs, refactors of any sizethe authoring seat itself: spawns a sub-agent reviewer in-session (/code-review), files the verdict on the PR as a COMMENT review, merges --rebase on a clean verdict
Class C / critical-path, Viska-internal (schema, migrations, gates/hooks, credentials + credential routing, cross-repo contracts, governance of merge authority)ViskaOps
Critical-path that leaves the universe (fleet-shared DDL, n8n infra deploy, Pantheon-owned surfaces)heph, async /viska-handoff only

"A build agent never merges its own PR" is RETIRED below Class C — the spawned sub-agent is the independent second pass, reading the diff cold. It stands for Class C, viska-pm included (the PM's own Class-C PRs go to ViskaOps). The verdict is a COMMENT review, never an APPROVE: GitHub blocks approval when author == reviewer, and every non-Ops write is viska-pm-app[bot]. Danger sets the class; universe-crossing sets the reviewer. Classify on what the diff governs, never on file type or size: a markdown/instruction change that rewrites credential routing, git-auth transport, or merge authority is Class C.

Enforcement posture (server-active as of 2026-06-10)#

Server-side branch protection is LIVE on all 5 org repos — a per-repo main-protection ruleset (id varies; enforcement: active, created 2026-06-10) that requires PR (required_approving_review_count: 0 — note this permits, but no longer authorizes, a self-merge; the reviewer table above is the binding constraint), allows only rebase/merge, and blocks non_fast_forward + deletion. PR-only-main is therefore server-enforced, not just discipline: a direct git push origin main (by anyone, App included) is rejected with GH013: Repository rule violations found / "Changes must be made through a pull request." (Verified 2026-07-01 — the ruleset live-rejected a viska-pm-app push to mastra main.) The bypass_actors list is empty by design: no identity bypasses PR-only-main, so any Action or script that needs to write main MUST open a PR and merge it, never push directly.

Consequence for automation: the graphify code-graph cadence Action (graphify-codegraph.yml) runs on non-main branches only (branches-ignore: main) and pushes the refreshed graph back to the pushed branch — the graph reaches main via the normal room→main PR. A manual workflow_dispatch on main will (correctly) fail at the commit-push step against this ruleset; that is the guard working, not a bug. (Historical note: this ruleset predates the graphify rollout, so no graphify Action ever actually pushed a refresh to main — the earlier "success" runs were no-ops where graph.json was already committed in the same push.)

The agency principle — condensed Pantheon#

When the agency builds a client system, it replicates parts of the Pantheon in condensed form: a single combined orchestrator (viska-pm = atlas + metis), its own memory plane (Viska-Wiki-Engineering, not Aion), its own handoff mailbox (Client Vault/_handoff/, not _DevSync/), and a slim skill set — not the full fleet. Per Clief Notes 60/30/10: skills are the 30% layer (repeatable rules), AGENTS.md routing is the highest-leverage artifact, and no single skill does everything.

Cross-refs#

  • Memory convention: viska-pm/AGENTS.md "Memory — the Viska Wiki" (NOT Aion). CLAUDE.md is a pointer only since 2026-07-28 — never cite it as a content source.
  • Handoff isolation: spec constellation-atlas/docs/superpowers/specs/2026-05-29-viska-client-warroom-platform-design.md §2a
  • Phase-2 isolation backlog: constellation-atlas/backlog/atlas-192