Compare commits

..

17 Commits

Author SHA1 Message Date
Svrnty
aeb17cce22 chore: sync Steev disclosure skills 2026-06-01 09:33:52 -04:00
Svrnty
c7b72a8758 chore: add Cortex child governance 2026-06-01 09:30:58 -04:00
Svrnty
0487a3d8fd Refine Steev profile disclosure and Proton tools 2026-05-30 23:35:53 -04:00
Svrnty
fdc27aa92f chore(steev): Wave 8.5 — strip chat_facing field (fiction — webui exposes all profiles to chat)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 19:55:17 -04:00
Svrnty
2491d48151 feat(steev): Wave 8 PAUSE-walk — apply Q4-Q10 + bte leak fix + proton-tools SKILL.md
Q4: confirm personal-scope discriminators (chat_facing, delegates_to=[ceo-planb], sovereign_only=false)
Q5: drop google-workspace cred — builtin manages own OAuth via Hermes hub (not credctl vault)
Q6: split proton-bridge-imap → proton-bridge-imap-user + proton-bridge-imap-pass (vault exact-match)
Q7: rename perplexity-api → perplexity (vault exact-match)
Q8: add 3 proton vault entries (account-email, account-password, mailbox-password)
Q9: install.sh F6 — MCP allowlist materialization; wires 3 proton MCPs, removes bte (hard-rule leak)
Q10: macOS-only externals annotated os_constraint:darwin; install.sh F7 emits INFO on non-Darwin

credbridge.sh: drop google-workspace case, rewrite proton-bridge to use 2 vault entries, rename perplexity case
Disclosure §7 rewritten with 6 credentials matching vault exact-name policy (DISCLOSURE-SCHEMA §4.5)
Disclosure §12 PAUSE table marked all 8 rows RESOLVED (rows 1-7 Wave 8, row 8 Wave 7)

Untracked skills/proton-tools/SKILL.md (90 lines, declared in manifest since Wave 4) — committed for clone-ability

Verified:
  hermes -p steev skills list → 6 enabled (matches disclosure.skills declaration)
  hermes -p steev mcp list → 3 entries (proton-calendar, proton-email, proton-contacts); bte removed
  F7 on Linux host correctly suppresses macOS-only externals

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 18:13:40 -04:00
Svrnty
959b8c8871 fix(install): R1 — COLUMNS=200 + untruncated awk parser for hermes skills list — Wave 7.5
Root cause: hermes 0.14 table renderer truncates skill names at column width
with unicode '…' suffix. Awk parser stripped '…' but couldn't recover the
truncated trailing chars (e.g., 'baoyu-article-illustr…' lost 'ator').
Fix: COLUMNS=200 env prefix forces wide table render → awk sees full names.

Affects both F2 (denylist write) and subrepo pre-push hook (drift check).
Re-run install.sh to refresh both per-profile config.yaml denylist + .git
/hooks/pre-push body.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 17:32:38 -04:00
Svrnty
57ef5411a4 feat(install): Wave 7.5 — steev F2b enable builtin allowlist via additive external_dirs — sprint 2026-05-25
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 17:20:11 -04:00
Svrnty
30d586e79e chore(bypass): Wave 7 schema-migration transition — drift expected per W7-DRIFT-RESIDUAL
enforcement-bypass: schema-migration — Wave 7 v1→v2 schema migration in flight; install.sh F2 denylist landed but additive external_dirs builtin-path additions deferred to Wave 8 per sot/01-ROADMAP/WAVE7-SPRINT-2026-05-25.md §"Wave 7 D7 — runtime verification findings"

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 17:11:11 -04:00
Svrnty
6f5ca6573c feat(install): Wave 7 D6 — steev install.sh disclosure→runtime + subrepo hook — sprint 2026-05-25 2026-05-24 16:59:17 -04:00
Svrnty
b85b266dcb feat(disclosure): Wave 7 D2 — steev schema v2 — sprint 2026-05-25 2026-05-24 16:53:16 -04:00
Svrnty
8e8ced470b feat(disclosure): Wave 4 — steev disclosure: block (CLAUDE.md hard-rule fix: REMOVE bte MCP) — sprint 2026-05-24
Applies Wave-3 auto-approved recommendations per
sot/06-REGISTRY/audits/RECOMMENDATIONS-steev-2026-05-24.md.

HARD-RULE FIX:
  - REMOVE bte MCP (inherit_mcp_toolsets: false + mcp_servers: []).
    bte = Plan B marketing platform; steev/CLAUDE.md:14 forbids access.

Auto-approved REMOVE/DROP:
  - 17 silently-inherited builtin skills denied (inherit_builtins: false).
  - Skills allowlist narrowed to 6: steev-agent, proton-tools, google-workspace,
    obsidian, himalaya, kanban-worker.

ADD (auto-approved):
  - schema_version: 1
  - inherit_builtins: false, inherit_mcp_toolsets: false

ADD (PAUSED-for-JP rows surfaced in DISCLOSURE.md §12):
  - Personal-scope discriminators (scope/chat_facing/delegates_to/sovereign_only)
    populated per audit §7d; values confirmation pending JP.
  - 3 cred name-mismatches kept as-declared in manifest; rename decision deferred
    (manifest vs vault vs bundle-indirection — W3.4 governance class).
  - 4 manifest-declared MCP installs (mcp_proton_*, mcp_perplexity) not registered;
    install ordering deferred.

Surface: 2 files only — steev/manifest.yaml + steev/DISCLOSURE.md.
sot-precommit --full-tree: EXIT 0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 15:59:58 -04:00
Svrnty
ff2b88a088 fix(steev): install.sh adds hermes-native profile install — dispatch-readiness
Mirror curator + CTO + CMO + CEO fix: append `hermes profile install "$REPO" --yes --force`.
Without this, steev couldn't register in kanban assignees registry.

Done block: verify skills + assignee + gateway-start hint.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 13:03:19 -04:00
Svrnty
7ea62147a6 feat(steev): PROFILE-DISTRIBUTION-PROTOCOL §2.1+§2.2 compliance + jp-voice stub
AGENT.md: REQUIRED T2 frontmatter per §2.1.
manifest.yaml: REQUIRED governance: block per §2.2 — org=personal, no
brand_master_ref (Steev is JP-scoped personal assistant).
.env.example: created (HERMES_HOME, STEEV_LIB, CREDCTL, STEEV_VAULT).
skills/steev-agent/jp-voice.md: stub placeholder w/ defaults (tone/cadence/
pronouns/filler-ban/honesty) + curated-samples slot + Plan B brand anti-
pattern guard. Replace as JP provides voice samples.
CLAUDE.md: site-map footer.

skills/proton-tools/ left untracked (pre-existing, out of scope this pass).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 12:36:54 -04:00
Svrnty
2db2d26250 feat(profile): §7 conformance build-out — credbridge, distribution.yaml, cron, manifest
Closes the largest set of PROFILE-DISTRIBUTION-PROTOCOL §7 readiness gaps
surfaced in the 2026-05-23 audit. Profile goes from 4/8 to expected 8/8
once skills/proton-tools/ is committed.

New files:

  credbridge.sh           Personal-assistant variant of the shared-core
                          credbridge pattern. Three credentials in scope:
                          google-workspace (Gmail/Calendar/Contacts),
                          proton-bridge (himalaya IMAP/SMTP), perplexity
                          (raw WebSearch). Plan B marketing platforms
                          explicitly OUT OF SCOPE per CLAUDE.md hard rule.

  validate_access.sh      Emits PASS/BLOCKED/FAIL JSON line per credential.
                          Sourceable from install.sh and standalone. Exit
                          code always 0; status is in the JSON.

  distribution.yaml       Native Hermes install contract (`hermes profile
                          install` reads this). Mirrors cmo/ceo pattern.
                          Documents personal/agnostic naming exception
                          per FRAMEWORK §6.1 — no org suffix because there
                          is exactly one principal.

  cron/steev-daily-briefing.json.template
                          06:30 daily briefing skeleton, ships disabled.
                          Aggregates calendar + flagged emails + due tasks
                          + carried items + brief news scan into a single
                          digest in JP's voice. NEVER auto-sends, NEVER
                          touches business comms (CEO → CMO surface).

manifest.yaml fully rewritten:

  - Added `contract: CONTRACT.md` pointer (was missing)
  - Added inline comment explaining intentional `org:` omission
  - Declared skills/proton-tools (on disk via JP's untracked WIP; declared
    here so manifest matches disk truth once JP commits it)
  - Added `lib:` block (credbridge.sh + validate_access.sh)
  - Added `expected_external_skills:` informational list (google-workspace,
    apple-*, obsidian, himalaya, imessage, perplexity) — these come from
    Hermes' global skills tree per CLAUDE.md "reuse existing core skills"
  - Added `optional_tools:` block (4 MCP servers: proton-calendar/-email/
    -contacts, perplexity)
  - Added `credentials:` block listing the 3 creds + resolution path
  - Promoted `cron:` from empty list to a single steev-daily-briefing
    entry (disabled_on_install: true)
  - Added `sovereignty:` block (qwen3.6-35b-a3b on DGX Spark)

CONTRACT.md frontmatter migrated from legacy `tier: S` to T1 per
FRONTMATTER-SPEC. Added required fields (name, last_reviewed,
description, depends_on).

skills/proton-tools/ left untracked — that's JP's WIP, not mine to
commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 19:01:55 -04:00
Svrnty
66c742c219 refactor(docs): rename STEEV-MASTER.md → CONTRACT.md (SOT taxonomy)
Per the workspace SOT classification: Tier S contracts use the canonical filename CONTRACT.md at repo root. STEEV-MASTER.md → steev/CONTRACT.md.

- File moved from docs/STEEV-MASTER.md to CONTRACT.md (repo root)
- Frontmatter note updated
- CLAUDE.md structure tree updated
- Empty docs/ directory removed

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 13:13:32 -04:00
Svrnty
0f5e807015 docs(frontmatter): tag STEEV-MASTER.md as tier S (profile contract)
Steev profile contract. Future rename: steev/CONTRACT.md per the SOT taxonomy.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 12:53:00 -04:00
Svrnty
fdd434c559 docs(claude): standardize CLAUDE.md — drop Karpathy block (now global), keep Steev-specific invariants
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 11:46:23 -04:00
16 changed files with 1202 additions and 86 deletions

8
.env.example Normal file
View File

@ -0,0 +1,8 @@
# Steev profile distribution — per-install config. Copy to .env and edit. NO SECRETS (those live in credctl).
HERMES_HOME="${HOME}/.hermes"
# The repo is canonical; ~/.hermes/steev symlinks to it (set by install.sh).
STEEV_LIB="${HOME}/.hermes/steev"
# Credential vault CLI (resolves secrets at call time; never stored here).
CREDCTL="${HOME}/workspaces/cortex/L6-svrnty.core-credentials/credctl"
# Obsidian vault location (Steev-only PKM; CMO/CEO never touch it).
STEEV_VAULT="${HOME}/vaults/steev"

View File

@ -1,3 +1,16 @@
---
name: steev-agent
tier: T2
status: active
owner: jp
source: hand
last_reviewed: 2026-05-24
description: Steev profile identity — JP's personal assistant / chief of staff (JP-scoped, not org-bound)
depends_on:
- profile-distribution-protocol
- steev-contract
---
# Steev — Agent Identity # Steev — Agent Identity
> The WHO of this profile distribution. Loaded conceptually before the orchestrator skill. For the full operating reference, see [`docs/STEEV-MASTER.md`](docs/STEEV-MASTER.md). > The WHO of this profile distribution. Loaded conceptually before the orchestrator skill. For the full operating reference, see [`docs/STEEV-MASTER.md`](docs/STEEV-MASTER.md).

32
AGENTS.md Normal file
View File

@ -0,0 +1,32 @@
# Steev Profile Agent Rules
This workspace is a child-local profile-workspace under the Cortex OS umbrella.
It is not Cortex OS Core authority. It is not a Cortex OS Instance. It is not a Runtime unless a governed Core route says so.
## Authority Order
1. `/home/svrnty/workspaces/cortex-os/core` active SOT.
2. `/home/svrnty/workspaces/cortex-os/core/AGENTS.md`.
3. This file.
4. `README.md`, `WORKBOARD.yaml`, and local tools.
5. Chat/session memory.
## Editing Rule
Keep work inside this workspace unless Core explicitly routes promotion.
After editing, run:
```bash
python3 tools/validate_steev_child.py
```
For governance text, follow Core caveman prose discipline.
## Protected Boundaries
- Do not mutate `../core/` from this workspace.
- Do not mutate sibling repositories.
- Do not import this workspace into Core source.
- Promotion into Core requires a governed Core route.

117
CLAUDE.md
View File

@ -1,87 +1,58 @@
# Working Principles # steev
## 1. Think Before Coding
Don't assume. Don't hide confusion. Surface tradeoffs.
Before implementing:
- State your assumptions explicitly. If uncertain, ask.
- If multiple interpretations exist, present them — don't pick silently.
- If a simpler approach exists, say so. Push back when warranted.
- If something is unclear, stop. Name what's confusing. Ask.
## 2. Simplicity First
Minimum code that solves the problem. Nothing speculative.
- No features beyond what was asked.
- No abstractions for single-use code.
- No "flexibility" or "configurability" that wasn't requested.
- No error handling for impossible scenarios.
- If you write 200 lines and it could be 50, rewrite it.
Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify.
## 3. Surgical Changes
Touch only what you must. Clean up only your own mess.
When editing existing code:
- Don't "improve" adjacent code, comments, or formatting.
- Don't refactor things that aren't broken.
- Match existing style, even if you'd do it differently.
- If you notice unrelated dead code, mention it — don't delete it.
When your changes create orphans:
- Remove imports/variables/functions that your changes made unused.
- Don't remove pre-existing dead code unless asked.
The test: Every changed line should trace directly to the user's request.
## 4. Goal-Driven Execution
Define success criteria. Loop until verified.
Transform tasks into verifiable goals:
- "Add validation" → "Write tests for invalid inputs, then make them pass"
- "Fix the bug" → "Write a test that reproduces it, then make it pass"
- "Refactor X" → "Ensure tests pass before and after"
For multi-step tasks, state a brief plan.
---
# Steev Profile Distribution
**Hermes classification:** profile distribution **Hermes classification:** profile distribution
*Inherits Karpathy 4 rules from `~/.claude/CLAUDE.md` — read them before coding.*
## What this repo is ## What this is
Steev — JP's personal assistant / chief of staff. Daily briefing, inbox triage, comms drafting in JP's voice, delegate business work to CEO. French/English bilingual. Steev — JP's personal assistant / chief of staff. Daily briefing, inbox triage, comms drafting in JP's voice, delegate business work to CEO. French/English bilingual. Sole chat touchpoint for JP.
## Key invariants ## Hard rules
- Steev drafts communications in JP's voice — NOT in Plan B brand voice (that's CMO) - Steev drafts in JP's voice — NEVER in Plan B brand voice (that's CMO's domain)
- Business tasks → delegate to CEO, never execute directly - Business tasks → delegate to CEO via kanban, never execute directly
- No access to Plan B marketing platform credentials - No access to Plan B marketing platform credentials (CMO-only)
- JP voice card lives at `skills/steev-agent/jp-voice.md` (create when JP provides samples) - JP voice card lives at `skills/steev-agent/jp-voice.md` (create when JP provides samples)
- `steev.db` is never committed — created by `install.sh`, managed at runtime - `steev.db` is never committed — created by `install.sh`, managed at runtime
- Obsidian vault = visual PKM at `~/vaults/steev` (Steev-only; CMO/CEO never touch it)
## Structure ## Structure
``` ```
steev/ steev/
├── manifest.yaml ├── manifest.yaml # profile: steev, kind: profile-distribution
├── AGENT.md ├── AGENT.md # Steev identity
├── CLAUDE.md ├── install.sh # idempotent installer → ~/.hermes/steev symlink
├── install.sh ├── skills/
├── skills/steev-agent/ │ ├── steev-agent/ # orchestrator skill (SKILL.md = source of truth)
│ └── SKILL.md │ └── obsidian-pkm/ # vault map + capture/triage conventions
├── schema.sql ├── schema.sql # steev.db schema
└── docs/ └── CONTRACT.md # Steev profile contract — tier S (this file wins)
└── STEEV-MASTER.md
``` ```
## Gotchas
- JP voice ≠ Plan B brand voice. Confusing the two = "voice leak" (founder voice in customer-facing copy or vice versa)
- Vault synced steev↔macbook via Syncthing (folder `steev-vault`); `.stignore` blocks `.obsidian/workspace*.json` + `.git`
- Reuse existing core skills (google-workspace, apple-notes, apple-reminders, obsidian) — only BUILD what doesn't exist (daily-briefing, inbox-triage, comms-drafting)
## Site map — where to find anything in cortex-os
Read these in order to ground any session:
| What | Where |
|---|---|
| **Karpathy 4 rules** | `~/.claude/CLAUDE.md` (auto-inherited every session) |
| **Workspace contract + repo map** | `~/workspaces/hermes/CLAUDE.md` |
| **SOT library orientation** | `~/workspaces/hermes/sot/README.md` |
| **Curator-generated SOT index** | `~/workspaces/hermes/sot/INDEX.md` |
| **Profile catalog (5 profiles + tool disclosure + governance)** | `~/workspaces/hermes/sot/06-REGISTRY/PROFILE-CATALOG.md` |
| **Profile distribution protocol (T1)** | `~/workspaces/hermes/sot/03-PROTOCOLS/PROFILE-DISTRIBUTION-PROTOCOL.md` |
| **Frontmatter spec (T1)** | `~/workspaces/hermes/sot/04-STANDARDS/FRONTMATTER-SPEC.md` |
| **SOT enforcement (pre-commit + curator + pre-push)** | `~/workspaces/hermes/sot/04-STANDARDS/SOT-ENFORCEMENT.md` |
| **Living graph artifact** | `~/workspaces/hermes/graph/umbrella.json` (curator-maintained) |
| **Living graph UI panel (planned)** | `/umbrella` route in hermes-webui per `sot/03-PROTOCOLS/CORTEX-OS-UMBRELLA-VIZ-PRD.md` |
| **This repo's CONTRACT.md** | `./CONTRACT.md` if present (T1 — wins over everything in this repo) |
If you're new to a session: read the workspace contract first, then this file, then the SOT orientation. Don't guess about cortex-os structure — anchor to these.

View File

@ -1,3 +1,17 @@
---
name: steev-contract
tier: T1
status: active
owner: jp
source: hand
last_reviewed: 2026-05-23
review_by: 2026-08-21
description: steev profile behavior contract — what Steev does, doesn't do, edge cases. Tier T1 — this file wins for the steev profile.
depends_on:
- profile-distribution-protocol
note: legacy tier S remapped to T1 per FRONTMATTER-SPEC 2026-05-23. Required fields filled (name, last_reviewed, description) per §7 audit.
---
# Steev — Source of Truth # Steev — Source of Truth
**Role:** Personal Assistant / Chief of Staff for JP (Mathias) **Role:** Personal Assistant / Chief of Staff for JP (Mathias)

153
DISCLOSURE.md Normal file
View File

@ -0,0 +1,153 @@
---
name: disclosure-steev
tier: T2
status: active
owner: jp
source: generated
last_reviewed: 2026-05-25
review_by: 2026-08-23
depends_on:
- disclosure-schema
- profile-distribution-protocol
description: Canonical disclosure of steev — exposed skills + MCP + sovereign APIs + cortex tools + credentials. Drift-checked vs live runtime by pre-push hook check 6.
auto_regen_cmd: "yq '.disclosure' manifest.yaml | <renderer-script>"
---
# `steev` — Disclosure
> Live as of `2026-05-25`. Disclosure schema v2 (manifest `disclosure.schema_version: 2` — adds `external_orchestrators` per DISCLOSURE-SCHEMA §4.6). Source: `steev/manifest.yaml → disclosure:` block. Pre-push hook check 6 (curator/lib/pre-push.sh) enforces this == live `hermes -p steev` runtime.
## §1 Identity
| Field | Value |
|---|---|
| Profile ID | `steev` |
| Repo | `/home/svrnty/workspaces/hermes/steev/` |
| Scope | `personal` |
| Org | `personal` |
| Owner | `jp` |
| Approval authority | `jp` |
| Role type | `personal-assistant` (Chief of Staff) |
| State | `stateful` (`steev.db` runtime-only, never committed) |
| Version | `1.0.0` |
| North star | keep JP unblocked — surface what needs attention, draft in JP voice, delegate business work to CEO |
| Chat-facing | `true` |
| Delegates to | `ceo-planb` |
| Sovereign-only | `false` |
## §2 Inheritance posture
| Field | Value | Rationale |
|---|---|---|
| `inherit_builtins` | `false` | Closes Wave-1 finding: 18 silently-enabled builtins (only `kanban-worker` cited in steev/ code — kept via explicit allowlist) |
| `inherit_mcp_toolsets` | `false` | **CLAUDE.md hard-rule fix.** Closes Wave-1 finding: `bte` MCP silently leaked from host. `bte` = Plan B marketing platform — forbidden to steev per `steev/CLAUDE.md:14` ("No access to Plan B marketing platform credentials (CMO-only)") |
| `inherit_dirs` | none | No external-dir skill bundles narrowed in |
| `sovereign_only` | `false` | steev intentionally calls Perplexity (hosted) for lightweight WebSearch per `manifest.yaml:90` — disclosed honestly |
| `external_orchestrators` | `[]` | Schema v2 field (DISCLOSURE-SCHEMA §4.6). steev has no exec'd orchestrators (no sandcastle equiv) — empty list. |
## §3 Skills (6)
Per `disclosure.skills` enum. Each row matches `hermes -p steev skills list` enabled set (pre-push check 6.a enforces).
| ID | Source | Role | Sovereign-req | Hosted-API | Justification |
|---|---|---|---|---|---|
| `steev-agent` | local | orchestrator | — | — | Orchestrator — daily briefing, inbox triage, comms drafting, delegate-to-CEO |
| `proton-tools` | local | toolkit | — | — | 24-tool Proton facade (Calendar+Email+Contacts) — JP-personal comms surface |
| `google-workspace` | builtin | engine | — | — | Gmail+Calendar+Contacts for daily briefing + inbox triage (manifest L46) |
| `obsidian` | builtin | engine | — | — | PKM vault at `~/vaults/steev` (CLAUDE.md L17) |
| `himalaya` | builtin | engine | — | — | IMAP/SMTP via proton-bridge (manifest L50) |
| `kanban-worker` | builtin | engine | — | — | CEO delegation transport — steev → ceo-planb (steev-agent SKILL.md L83) |
**Totals.** 6 skills total. Source breakdown: 2 local, 0 hub, 4 builtin, 0 external_dir.
**Wave-1 → Wave-4 delta.** Live `hermes -p steev skills list` showed 21 enabled (2 local + 18 builtins +/- the 7 declared external set). Wave-4 narrows to 6 — drops 17 inherited builtins (15 uncited; 8 of the 17 are CONTRACT.md §9 v2+ REUSE candidates re-added when v2 lands).
## §4 MCP servers (0)
No MCP servers exposed — deny-by-default allowlist is empty.
**Wave-1 → Wave-4 delta.** Live `hermes -p steev mcp list` showed `bte` registered + enabled (silently inherited via host-global `agent.inherit_mcp_toolsets: true`). Wave-4 sets `inherit_mcp_toolsets: false` and omits `bte` from the allowlist — **resolves CLAUDE.md hard-rule violation**. Four manifest-declared MCP installs (`mcp_proton_calendar`, `mcp_proton_email`, `mcp_proton_contacts`, `mcp_perplexity`) are NOT registered today; ADD-back deferred (see §12).
## §5 Sovereign APIs (0)
No direct HTTP/gRPC sovereign API calls. Indirect access flows through the (currently unregistered) Proton/Perplexity MCP servers.
## §6 Cortex tools (0)
No `cortex/L6-*` or `cortex/PG-*` libraries consumed at runtime. `lib/` scripts (`credbridge.sh`, `validate_access.sh`) are repo-local utility shims, not cortex tools.
## §7 Credentials (6 declared)
Per `disclosure.credentials` allowlist. Names + scopes only — NEVER values. Pre-push check 6.d enforces vault_name exact-match. **Wave 8 (2026-05-24): aligned with vault.**
| Vault name | Status | Scope | Used by | Governance |
|---|---|---|---|---|
| `proton-bridge-imap-user` | required | read | `credbridge.sh` | JP-personal; local Proton Bridge IMAP/SMTP username (himalaya path) |
| `proton-bridge-imap-pass` | required | read | `credbridge.sh` | JP-personal; local Proton Bridge IMAP/SMTP password (himalaya path) |
| `perplexity` | optional | read | `credbridge.sh` | JP-personal; WebSearch fallback (MCP path preferred) |
| `proton-account-email` | required | read | `credbridge.sh`, `mcp_proton_email` | JP-personal; Proton account email (consumed by proton-email MCP server) |
| `proton-account-password` | required | read | `credbridge.sh`, `mcp_proton_email` | JP-personal; Proton account password (consumed by proton-email MCP server) |
| `proton-mailbox-password` | required | read | `credbridge.sh`, `mcp_proton_email` | JP-personal; Proton mailbox E2E key for mail decryption |
> **google-workspace removed Wave 8** — Hermes builtin `google-workspace` skill manages its own OAuth flow via Hermes hub, not credctl vault. credbridge.sh google-workspace case dropped accordingly.
## §8 Cron (1)
| Job | Schedule | Skill | Disabled on install |
|---|---|---|---|
| `steev-daily-briefing` | `30 6 * * *` (06:30 local) | `steev-agent` | `true` (per §6 Safety) |
## §9 Drift status
| Surface | Declared | Live (Wave-1) | Status |
|---|---|---|---|
| Skills | 6 | 21 enabled | drift expected post-Wave-4 reinstall → in-sync |
| MCP servers | 0 | 1 (`bte`) | drift — Wave-4 reinstall removes `bte`; pending install.sh patch + reinstall |
| MCP tools (total) | 0 | n/a (`bte` is `all`-tools) | n/a after MCP removal |
| Credentials | 3 | 3 declared, 3 vault-name mismatches | name-canonicalization drift (PENDING JP, §12) |
> Pre-push hook check 6 last run: not yet — Wave-4 inserts the check; first run validates this disclosure after `install.sh` reapplies `disclosure.*` to `~/.hermes/profiles/steev/config.yaml`.
## §10 Sovereign-purity audit
- Steev's owned code (`steev/skills/`, `steev/lib/`): **CLEAN** — only Proton + Google Workspace + Perplexity (last is hosted but `sovereign_only: false` discloses honestly).
- Bundled-skill exposure layer: **CLEAN post-Wave-4** — 17 builtins removed; only 4 builtins allowlisted (google-workspace, obsidian, himalaya, kanban-worker), none hosted-API.
- `sovereign_only: false` — validator rule 6.e does not apply.
## §11 Governance refs
- Vision: `../sot/01-ROADMAP/CORTEX-OS-ROADMAP.md`, `../sot/02-FRAMEWORK/CORTEX-OS-FRAMEWORK.md`
- Governing protocols: `../sot/03-PROTOCOLS/PROFILE-DISTRIBUTION-PROTOCOL.md`
- Standards: `../sot/04-STANDARDS/FRONTMATTER-SPEC.md`, `../sot/04-STANDARDS/SOT-ENFORCEMENT.md`, `../sot/04-STANDARDS/DISCLOSURE-SCHEMA.md`
- Brand master ref: omitted (scope: personal) — steev serves JP personally, not a brand/org
## §12 Open issues + next steps
All 8 Wave-3 PAUSE rows resolved in **Wave 8 (2026-05-24)**. Audit trail retained below.
| # | Topic | Resolution | Wave |
|---|---|---|---|
| 1 | Personal-scope discriminator values (`chat_facing: true`, `delegates_to: [ceo-planb]`, `sovereign_only: false`) | **CONFIRMED** (Q4). Matches CLAUDE.md L7-L8 + CONTRACT delegation chain. | 8 |
| 2 | Cred `google-workspace` not in vault | **REMOVED** (Q5 + scope-expansion). Builtin manages own OAuth via Hermes hub; no credctl vault entry needed. credbridge.sh google-workspace case dropped. | 8 |
| 3 | Cred `proton-bridge-imap` vs vault `proton-bridge-imap-pass` + `proton-bridge-imap-user` | **SPLIT** (Q6). Manifest split into 2 entries matching vault. credbridge.sh exports both `PROTON_BRIDGE_IMAP_USER` + `PROTON_BRIDGE_IMAP_PASSWORD`. | 8 |
| 4 | Cred `perplexity-api` vs vault `perplexity` | **RENAMED** (Q7). Manifest + credbridge.sh updated to `perplexity` (exact-match per schema §4.5). | 8 |
| 5 | 3 proton vault entries undeclared (`proton-account-email`, `proton-account-password`, `proton-mailbox-password`) | **ADDED** (Q8). Declared in `disclosure.credentials` w/ `used_by: [credbridge.sh, mcp_proton_email]`. The other 2 (`proton-bridge-imap-pass/-user`) covered by row 3. | 8 |
| 6 | 4 declared MCP servers absent from `hermes mcp list` (`mcp_proton_calendar`, `mcp_proton_email`, `mcp_proton_contacts`, `mcp_perplexity`) | **MATERIALIZED 3/4** (Q9). install.sh F6 wires 3 proton MCPs into per-profile config from `optional_tools`. Also removed bte (hard-rule leak discovered Wave 8). mcp_perplexity DEFERRED (server not in global `hermes mcp list`). | 8 |
| 7 | macOS-only externals (`apple-notes`, `apple-reminders`, `imessage`) in `expected_external_skills` | **OS-GATED** (Q10). Annotated `os_constraint: darwin`. install.sh F7 emits INFO on non-Darwin hosts that these are unavailable. | 8 |
| 8 | Pre-push hook check 6 not yet wired (curator/lib/pre-push.sh patch belongs to Wave-5+) | **WIRED** (Wave 7 D6). Subrepo pre-push hook installed via `install.sh F4`; main repo hook covers 6.a-6.f. | 7 |
### Wave 8 follow-ups (not PAUSE — separate work)
- **mcp_perplexity install** — server doesn't exist in global `hermes mcp list`. When provisioned, install.sh F6 will materialize automatically (no code change).
- **Per-tool enumeration in `disclosure.mcp_servers`** — currently `[]` w/ install.sh F6 driven from `optional_tools`. Wave 8.5: introspect each MCP server, populate `disclosure.mcp_servers[*].tools[]` per DISCLOSURE-SCHEMA §4.2.
## §13 Related
- [`../sot/04-STANDARDS/DISCLOSURE-SCHEMA.md`](../sot/04-STANDARDS/DISCLOSURE-SCHEMA.md) — schema definition
- [`../sot/03-PROTOCOLS/PROFILE-DISTRIBUTION-PROTOCOL.md`](../sot/03-PROTOCOLS/PROFILE-DISTRIBUTION-PROTOCOL.md) — protocol disclosure extends
- [`../sot/06-REGISTRY/PROFILE-CATALOG.md`](../sot/06-REGISTRY/PROFILE-CATALOG.md) — fleet rollup (aggregates this doc + 4 siblings)
- [`../sot/06-REGISTRY/audits/AUDIT-steev-2026-05-24.md`](../sot/06-REGISTRY/audits/AUDIT-steev-2026-05-24.md) — Wave-1 discovery
- [`../sot/06-REGISTRY/audits/RECOMMENDATIONS-steev-2026-05-24.md`](../sot/06-REGISTRY/audits/RECOMMENDATIONS-steev-2026-05-24.md) — Wave-3 recommendations
- `./manifest.yaml` — machine-readable `disclosure:` block
- `./AGENT.md` — identity (T2)
- `./CONTRACT.md` — behavior contract (T1)

6
WORKBOARD.yaml Normal file
View File

@ -0,0 +1,6 @@
items:
- id: STEEV-WORK-001
title: Centralized Legacy Workspace Review
status: candidate
source: README.md
owner: jp

81
credbridge.sh Executable file
View File

@ -0,0 +1,81 @@
#!/usr/bin/env bash
# credbridge.sh — resolve credctl credentials into env vars for steev's
# personal-flow tools, then exec the CLI. Secrets NEVER echoed, logged, or
# written to disk.
#
# Usage: credbridge.sh <tool> [args...]
# tools: proton-bridge | perplexity
#
# Per PROFILE-DISTRIBUTION-PROTOCOL §3 (shared core, "credbridge" row) and §6
# (Conventions → Secrets), every profile distribution exposes credentials via
# this verb chain: credctl set → credenv → exec, with secrets per-call only.
#
# This is the personal-assistant variant of the credbridge pattern. Steev's
# cred surface is narrow by design:
#
# - proton-bridge: IMAP/SMTP user + password for the local Proton Bridge
# T6 sidecar — gives Steev access to JP's Proton mail
# via himalaya (cleartext on 127.0.0.1 only)
# - perplexity: Perplexity API key for WebSearch toolset (lightweight
# — most Steev work uses the perplexity MCP instead)
#
# Wave 8 (2026-05-24): google-workspace case REMOVED — Hermes builtin
# google-workspace skill manages its own OAuth flow via the Hermes hub, not
# the credctl vault. Vault contains no google-workspace-* entries.
#
# Plan B marketing platforms (WooCommerce, Mailchimp, Meta, GA4, etc.) are OUT
# OF SCOPE here — that's cmo-planb's credbridge. Steev MUST NEVER resolve a
# marketing platform credential. The CLAUDE.md "no access to Plan B marketing
# platform credentials" rule is enforced by omission from this case statement.
#
# Design notes (same as cmo/credbridge.sh — shared core):
# - credctl values read into local vars, exported straight to the child env
# - No `echo $secret`. set +x stays off.
set -euo pipefail
# Paths env-overridable for portability; defaults match the JP install.
CREDCTL="${CREDCTL:-/home/svrnty/workspaces/cortex/L6-svrnty.core-credentials/credctl}"
STEEV_LIB="${STEEV_LIB:-/home/svrnty/.hermes/steev}"
die() { printf '{"error":"%s"}\n' "$1" >&2; exit 1; }
[ $# -ge 1 ] || die "usage: credbridge.sh <proton-bridge|perplexity> [args...]"
TOOL="$1"; shift
[ -x "$CREDCTL" ] || die "credctl not found/executable at $CREDCTL"
# cred_raw <name> — print unmasked credential value to stdout. Caller captures
# into a var; value never displayed. Strips the "Value:" header from credctl.
cred_raw() {
"$CREDCTL" get "$1" --unmask 2>/dev/null \
| sed -n '/^Value:/,$p' | sed '1s/^Value:[[:space:]]*//'
}
case "$TOOL" in
proton-bridge)
# Steev reads JP's Proton inbox via the local Proton Bridge IMAP daemon
# (T6 sidecar — see PROFILE-DISTRIBUTION-PROTOCOL §4.T6). credctl stores
# user + password as separate vault entries (Wave 8 aligned to vault).
PB_USER="$(cred_raw proton-bridge-imap-user)"
[ -n "$PB_USER" ] || die "credctl: proton-bridge-imap-user not set"
PB_PASS="$(cred_raw proton-bridge-imap-pass)"
[ -n "$PB_PASS" ] || die "credctl: proton-bridge-imap-pass not set"
export PROTON_BRIDGE_IMAP_USER="$PB_USER"
export PROTON_BRIDGE_IMAP_PASSWORD="$PB_PASS"
exec "$@"
;;
perplexity)
# Lightweight WebSearch path. Most Steev research goes through the
# perplexity MCP server (which holds its own key); this credbridge entry
# exists for scripts that need a raw key (rare). Wave 8 renamed
# vault entry `perplexity-api` → `perplexity`.
PPL_KEY="$(cred_raw perplexity)"
[ -n "$PPL_KEY" ] || die "credctl: perplexity not set"
export PERPLEXITY_API_KEY="$PPL_KEY"
exec "$@"
;;
*)
die "unknown tool: $TOOL (allowed: proton-bridge|perplexity)"
;;
esac

View File

@ -0,0 +1,40 @@
{
"id": "steev-daily-briefing",
"name": "steev-daily-briefing",
"prompt": "Steev daily briefing. DB=${HERMES_HOME}/steev/steev.db, helper=${HERMES_HOME}/steev/steev_db.py.\n1. runtime-get: if agent_runtime.status='halted' → STOP, output \"halted, skipping\", do nothing else.\n2. Aggregate today's context:\n - calendar events from google-workspace (today + tomorrow morning)\n - flagged + unread emails from proton-bridge (himalaya) + google-workspace (gmail)\n - due tasks from apple-reminders + obsidian vault (~/vaults/steev/Tasks/)\n - carried items from steev.db (briefings.carried_over)\n - brief news scan via perplexity (1-2 items only)\n3. Compose a single digest in JP's voice (FR/EN per JP's language preference for that day). Surface only what needs JP's attention.\n4. Persist digest to steev.db (briefings table) as status='draft'.\n5. Output a 5W summary of items surfaced.\n6. NEVER auto-send. NEVER touch business comms surface (that's CEO → CMO).",
"skills": [
"steev-agent"
],
"skill": "steev-agent",
"model": null,
"provider": null,
"base_url": null,
"script": null,
"no_agent": false,
"context_from": null,
"schedule": {
"kind": "cron",
"expr": "30 6 * * *",
"display": "06:30 daily"
},
"schedule_display": "06:30 daily",
"repeat": {
"times": null,
"completed": 0
},
"enabled": false,
"state": "paused",
"paused_at": null,
"paused_reason": "installed paused — enable via: hermes cron resume steev-daily-briefing",
"created_at": null,
"next_run_at": null,
"last_run_at": null,
"last_status": null,
"last_error": null,
"last_delivery_error": null,
"deliver": "local",
"origin": null,
"enabled_toolsets": null,
"workdir": "${HERMES_HOME}/steev",
"profile": "steev"
}

29
distribution.yaml Normal file
View File

@ -0,0 +1,29 @@
# Hermes profile distribution manifest — native Hermes install contract.
# Used by `hermes profile install`. Distinct from manifest.yaml (our workspace
# convention layered on top — see ../sot/03-PROTOCOLS/PROFILE-DISTRIBUTION-PROTOCOL.md).
name: steev
version: 1.0.0
description: "Steev — JP's personal AI chief of staff. Daily briefing (calendar + flagged emails + due tasks + carried items + brief news scan), inbox triage, comms drafting in JP's voice (French/English bilingual), and business-task delegation to ceo-planb. Personal-flow manager — no Plan B marketing surface. Sovereign on qwen3.6-35b-a3b."
hermes_requires: ">=0.14.0"
author: "Svrnty / JP <mathias@openharbor.io>"
license: "proprietary"
# Steev is personal/agnostic per CORTEX-OS-FRAMEWORK §6.1 — no org suffix.
# Profile name = `steev` (not `steev-<org>`) because there is exactly one
# principal: JP. Cloning steev for another principal = rename in
# distribution.yaml only; no other code changes.
env_requires: [] # credentials provisioned via credctl at install/runtime, never in env
distribution_owned:
- AGENT.md
- CONTRACT.md
- CLAUDE.md
- README.md
- manifest.yaml
- schema.sql
- install.sh
- credbridge.sh
- validate_access.sh
- skills/
- cron/

View File

@ -56,4 +56,337 @@ fi
echo "== steev.db ==" echo "== steev.db =="
run "sqlite3 '$HERMES_HOME/steev/steev.db' < '$REPO/schema.sql'" run "sqlite3 '$HERMES_HOME/steev/steev.db' < '$REPO/schema.sql'"
echo "== done. verify: hermes -p steev skills list | grep steev-agent ==" echo ""
echo "== hermes-native profile install (dispatch-readiness) =="
if [ "$DRY" = 1 ]; then
echo "DRY: hermes profile install '$REPO' --yes --force"
else
hermes profile install "$REPO" --yes --force 2>&1 | tail -5 || \
echo " WARN: hermes profile install failed (legacy symlink still works)"
fi
echo ""
# ----------------------------------------------------------------------------
# Wave 7 D6 — disclosure → runtime config wiring (F1-F4)
# Materializes manifest.disclosure (schema v2) into the live Hermes runtime:
# F1 resolve $HERMES_WORKSPACE in inherit_dirs → skills.external_dirs (global)
# F2 compute denylist from disclosure.skills → skills.disabled (per-profile)
# F3 propagate inherit_mcp_toolsets → agent.inherit_mcp_toolsets
# F4 install subrepo pre-push disclosure-drift gate
# Steev = personal scope: profile name is `steev` (no -planb suffix per FRAMEWORK §6.1).
# All steps idempotent + graceful (WARN + skip on missing tooling).
# ----------------------------------------------------------------------------
echo "== disclosure → runtime config (Wave 7 D6) =="
PROFILE_NAME="steev"
HERMES_WORKSPACE="${HERMES_WORKSPACE:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}"
GLOBAL_CFG="$HERMES_HOME/config.yaml"
PROFILE_CFG="$HOME/.hermes/profiles/$PROFILE_NAME/config.yaml"
# F1 — resolve $HERMES_WORKSPACE in disclosure.inherit_dirs → skills.external_dirs (global config)
if [ "$DRY" = 1 ]; then
echo "DRY: F1 expand inherit_dirs (HERMES_WORKSPACE=$HERMES_WORKSPACE) → $GLOBAL_CFG"
elif command -v yq >/dev/null 2>&1; then
INHERIT_DIRS=$(yq -r '.disclosure.inherit_dirs[]?' "$REPO/manifest.yaml" 2>/dev/null || true)
if [ -n "$INHERIT_DIRS" ]; then
while IFS= read -r raw; do
[ -z "$raw" ] && continue
resolved="${raw//\$HERMES_WORKSPACE/$HERMES_WORKSPACE}"
if [ ! -d "$resolved" ]; then
echo " WARN: F1 inherit_dir not found: $resolved (declared: $raw) — skipping"
continue
fi
python3 - "$GLOBAL_CFG" "$resolved" <<'PY'
import sys, os, yaml
cfg, sk = sys.argv[1], sys.argv[2]
d = yaml.safe_load(open(cfg).read()) if os.path.exists(cfg) else {}
d = d or {}
d.setdefault('skills', {}).setdefault('external_dirs', [])
if sk not in d['skills']['external_dirs']:
d['skills']['external_dirs'].append(sk)
open(cfg, 'w').write(yaml.safe_dump(d, sort_keys=False, allow_unicode=True))
print(f" F1 + {sk}")
else:
print(f" F1 = {sk} (already present)")
PY
done <<< "$INHERIT_DIRS"
else
echo " F1: no disclosure.inherit_dirs declared — skip"
fi
else
echo " WARN: F1 yq not on PATH — skipping inherit_dirs resolution"
fi
# F2 — compute denylist (all builtins NOT in disclosure.skills allowlist) → profile config
if [ "$DRY" = 1 ]; then
echo "DRY: F2 compute builtin denylist → $PROFILE_CFG (skills.disabled)"
elif command -v hermes >/dev/null 2>&1 && command -v yq >/dev/null 2>&1; then
# Try --json first; fall back to table parse w/ box-draw chars (Wave 5 parser).
ALL_BUILTINS=$(hermes skills list --json 2>/dev/null | jq -r '.[] | select(.source=="builtin") | .name' 2>/dev/null || true)
if [ -z "$ALL_BUILTINS" ]; then
ALL_BUILTINS=$(COLUMNS=200 hermes skills list 2>/dev/null | awk -F'│' 'NR>3 && /builtin/ {name=$2; gsub(/^[[:space:]]+|[[:space:]]+$/, "", name); gsub(/…$/, "", name); print name}' || true)
fi
ALLOWLIST_BUILTIN=$(yq -r '.disclosure.skills[] | select(.source=="builtin") | .id' "$REPO/manifest.yaml" 2>/dev/null | sort -u)
if [ -z "$ALL_BUILTINS" ]; then
echo " WARN: F2 could not enumerate live builtins — skipping denylist"
else
SORTED_BUILTINS=$(echo "$ALL_BUILTINS" | sort -u)
DENYLIST=$(comm -23 <(echo "$SORTED_BUILTINS") <(echo "${ALLOWLIST_BUILTIN:-}") | grep -v '^$' || true)
mkdir -p "$(dirname "$PROFILE_CFG")"
[ -f "$PROFILE_CFG" ] || : > "$PROFILE_CFG"
python3 - "$PROFILE_CFG" <<PY
import sys, yaml
cfg = sys.argv[1]
denylist = [s for s in """$DENYLIST""".splitlines() if s.strip()]
d = yaml.safe_load(open(cfg).read()) or {}
d.setdefault('skills', {})['disabled'] = sorted(set(denylist))
open(cfg, 'w').write(yaml.safe_dump(d, sort_keys=False, allow_unicode=True))
print(f" F2 wrote skills.disabled: {len(denylist)} entr{'y' if len(denylist)==1 else 'ies'}")
PY
fi
else
echo " WARN: F2 hermes/yq missing — skipping denylist"
fi
# F2b — enable builtin allowlist via additive external_dirs
# Hermes 0.14 uses additive external_dirs model (not pure denylist) — to enable
# a builtin skill, add its hermes-agent/skills/<category>/<skill> path here.
HERMES_AGENT_SKILLS="$HERMES_WORKSPACE/hermes-agent/skills"
if [ "$DRY" = 1 ]; then
echo "DRY: F2b enable builtin allowlist via additive external_dirs → $PROFILE_CFG"
elif command -v yq >/dev/null 2>&1; then
BUILTIN_PATHS=$(yq -r '.disclosure.skills[]? | select(.source=="builtin") | .path' "$REPO/manifest.yaml" 2>/dev/null || true)
BUILTIN_ENABLED=0
for p in $BUILTIN_PATHS; do
full="$HERMES_AGENT_SKILLS/$p"
if [ -d "$full" ]; then
if ! yq -r '.skills.external_dirs[]?' "$PROFILE_CFG" 2>/dev/null | grep -qF "$full"; then
mkdir -p "$(dirname "$PROFILE_CFG")"
full="$full" yq -i '.skills.external_dirs += [env(full)]' "$PROFILE_CFG" \
|| echo " WARN: F2b yq write to $PROFILE_CFG failed for $full"
BUILTIN_ENABLED=$((BUILTIN_ENABLED + 1))
fi
else
echo " ⚠ F2b: builtin path missing — $full (skipped)" >&2
fi
done
[ "$BUILTIN_ENABLED" -gt 0 ] && echo " F2b enabled $BUILTIN_ENABLED builtin allowlist path(s) in external_dirs"
else
echo " WARN: F2b yq not on PATH — skipping builtin allowlist"
fi
# F3 — propagate disclosure.inherit_mcp_toolsets to per-profile config.yaml
if [ "$DRY" = 1 ]; then
echo "DRY: F3 write agent.inherit_mcp_toolsets → $PROFILE_CFG"
elif command -v yq >/dev/null 2>&1; then
INHERIT_MCP=$(yq -r '.disclosure.inherit_mcp_toolsets' "$REPO/manifest.yaml" 2>/dev/null || echo "null")
if [ "$INHERIT_MCP" = "null" ] || [ -z "$INHERIT_MCP" ]; then
echo " F3: disclosure.inherit_mcp_toolsets undeclared — skip"
else
mkdir -p "$(dirname "$PROFILE_CFG")"
[ -f "$PROFILE_CFG" ] || : > "$PROFILE_CFG"
python3 - "$PROFILE_CFG" "$INHERIT_MCP" <<'PY'
import sys, yaml
cfg, val = sys.argv[1], sys.argv[2]
b = {"true": True, "false": False}.get(val.lower(), val)
d = yaml.safe_load(open(cfg).read()) or {}
d.setdefault('agent', {})['inherit_mcp_toolsets'] = b
open(cfg, 'w').write(yaml.safe_dump(d, sort_keys=False, allow_unicode=True))
print(f" F3 wrote agent.inherit_mcp_toolsets: {b}")
PY
fi
else
echo " WARN: F3 yq not on PATH — skipping inherit_mcp_toolsets"
fi
# F4 — install subrepo pre-push hook (disclosure-drift gate)
HOOK_DST="$REPO/.git/hooks/pre-push"
if [ "$DRY" = 1 ]; then
echo "DRY: F4 install pre-push hook → $HOOK_DST"
elif [ ! -d "$REPO/.git" ]; then
echo " WARN: F4 $REPO/.git missing — not a git checkout, skip"
else
cat > "$HOOK_DST" <<'HOOK_EOF'
#!/usr/bin/env bash
# pre-push.sh — Wave 7 D6 subrepo disclosure-drift gate
# Schema v2 ref: ../sot/04-STANDARDS/DISCLOSURE-SCHEMA.md
set -euo pipefail
REPO_ROOT="$(git rev-parse --show-toplevel)"
PROFILE_DIR_NAME="$(basename "$REPO_ROOT")" # cmo | ceo | cto | steev | curator
PROFILE_NAME="${PROFILE_DIR_NAME}-planb" # default: org-scoped C-suite naming
# Personal-scope profiles drop the -planb suffix (FRAMEWORK §6.1).
[ "$PROFILE_DIR_NAME" = "steev" ] && PROFILE_NAME="steev"
VIOLATIONS=0
emit() { echo "[pre-push:$PROFILE_NAME] $*" >&2; }
fail() { emit "BLOCK: $*"; VIOLATIONS=$((VIOLATIONS + 1)); }
while read -r local_ref local_sha remote_ref remote_sha; do
[ "$local_sha" = "0000000000000000000000000000000000000000" ] && continue
if [ "$remote_sha" = "0000000000000000000000000000000000000000" ]; then
base=$(git merge-base "$local_sha" jp 2>/dev/null || git rev-parse "$local_sha^" 2>/dev/null || echo "$local_sha")
else
base="$remote_sha"
fi
DELTA=$(git diff --name-only "$base..$local_sha" 2>/dev/null || true)
[ -z "$DELTA" ] && continue
# check 2: manifest governance block
if echo "$DELTA" | grep -q '^manifest\.yaml$'; then
emit "check 2: validating governance block…"
python3 - "$REPO_ROOT/manifest.yaml" <<'PY' || fail "manifest.yaml: governance block invalid"
import sys, yaml
data = yaml.safe_load(open(sys.argv[1]).read()) or {}
gov = data.get("governance")
if not isinstance(gov, dict):
print(f"missing governance: block", file=sys.stderr); sys.exit(1)
req = ["org", "owner", "approval_authority", "vision_refs", "governing_protocols", "standards", "north_star"]
missing = [k for k in req if k not in gov]
if missing:
print(f"governance missing: {', '.join(missing)}", file=sys.stderr); sys.exit(1)
PY
fi
# check 3: identity doc frontmatter
CHANGED_IDENTITY=$(echo "$DELTA" | grep -E '^(AGENT|CONTRACT|DISCLOSURE)\.md$' || true)
if [ -n "$CHANGED_IDENTITY" ]; then
emit "check 3: validating frontmatter…"
for f in $CHANGED_IDENTITY; do
python3 - "$REPO_ROOT/$f" <<'PY' || fail "$f: frontmatter invalid"
import sys, re, yaml
content = open(sys.argv[1]).read()
m = re.match(r"^---\n(.*?)\n---", content, re.DOTALL)
if not m: print(f"missing YAML frontmatter", file=sys.stderr); sys.exit(1)
fm = yaml.safe_load(m.group(1)) or {}
req = {"name", "tier", "status", "owner", "source", "last_reviewed", "description"}
missing = req - set(fm.keys())
if missing: print(f"frontmatter missing: {', '.join(missing)}", file=sys.stderr); sys.exit(1)
PY
done
fi
# check 6: disclosure drift (only if manifest changed)
if echo "$DELTA" | grep -q '^manifest\.yaml$'; then
emit "check 6: disclosure drift…"
# 6.a skills drift
if command -v hermes >/dev/null 2>&1; then
declared=$(yq -r '.disclosure.skills[].id' "$REPO_ROOT/manifest.yaml" 2>/dev/null | sort -u)
live=$(COLUMNS=200 hermes -p "$PROFILE_NAME" skills list 2>/dev/null | awk -F'│' 'NF>=6 && $(NF-1) ~ /enabled[[:space:]]*$/ {name=$2; gsub(/^[[:space:]]+|[[:space:]]+$/, "", name); gsub(/…$/, "", name); if (name ~ /^[a-z]/) print name}' | sort -u || echo "")
if [ -n "$live" ]; then
drift=$(diff <(echo "$declared") <(echo "$live") 2>/dev/null || true)
[ -n "$drift" ] && fail "skills drift: $drift"
else
emit " (skip 6.a — hermes CLI live-skill query returned empty; WARN)"
fi
else
emit " (skip 6.a — hermes CLI not on PATH; WARN)"
fi
# 6.b/6.c/6.d/6.e simplified: just confirm manifest yq parses + sovereign_only invariant
if [ "$(yq '.disclosure.sovereign_only' "$REPO_ROOT/manifest.yaml")" = "true" ]; then
bad=$(yq -r '.disclosure.skills[] | select(.hosted_api != null) | .id' "$REPO_ROOT/manifest.yaml" 2>/dev/null)
[ -n "$bad" ] && fail "sovereign_only=true but skills declare hosted_api: $bad"
fi
fi
# bypass marker categorization
for sha in $(git rev-list "$base..$local_sha"); do
MSG=$(git log -1 --format=%B "$sha")
bypass_line=$(echo "$MSG" | grep -E '^enforcement-bypass:' || true)
if [ -n "$bypass_line" ]; then
if ! echo "$bypass_line" | grep -qE '^enforcement-bypass: (emergency|upstream-blocker|schema-migration|hermes-bug|third-party-bug) — '; then
fail "$sha: bypass marker uncategorized — required: 'enforcement-bypass: <CATEGORY> — <line>'"
fi
fi
done
done
if [ "$VIOLATIONS" -gt 0 ]; then
emit "$VIOLATIONS violation(s) — push blocked"
exit 1
fi
emit "✓ subrepo pre-push gate passed"
exit 0
HOOK_EOF
chmod +x "$HOOK_DST"
echo " F4 installed: $HOOK_DST"
fi
# F6 — MCP server materialization (Wave 8 Q9)
# Reads manifest.optional_tools (mcp_<server-name-with-underscores> aliases),
# maps to runtime MCP server names (hyphenated), copies global config block
# into per-profile config.yaml. Removes non-declared MCPs (closes bte leak).
if [ "$DRY" = 1 ]; then
echo "DRY: F6 materialize MCP allowlist → $PROFILE_CFG"
elif command -v yq >/dev/null 2>&1 && [ -f "$HERMES_HOME/config.yaml" ]; then
# Declared MCP set (mcp_proton_calendar → proton-calendar etc).
DECLARED_MCPS=$(yq -r '.optional_tools[]?' "$REPO/manifest.yaml" 2>/dev/null | sed 's/^mcp_//; s/_/-/g')
if [ -z "$DECLARED_MCPS" ]; then
echo " F6: no optional_tools declared — skip"
else
mkdir -p "$(dirname "$PROFILE_CFG")"
[ -f "$PROFILE_CFG" ] || : > "$PROFILE_CFG"
F6_ADDED=0; F6_REMOVED=0; F6_MISSING=0
# Set the per-profile mcp_servers block from the declared list. Existing
# entries NOT in declared list are dropped (denylist enforcement).
GLOBAL_CFG="$HERMES_HOME/config.yaml"
python3 - "$GLOBAL_CFG" "$PROFILE_CFG" "$DECLARED_MCPS" <<'PY'
import sys, yaml
gcfg, pcfg, declared_str = sys.argv[1], sys.argv[2], sys.argv[3]
declared = [s.strip() for s in declared_str.splitlines() if s.strip()]
g = yaml.safe_load(open(gcfg).read()) or {}
p = yaml.safe_load(open(pcfg).read()) or {}
g_mcps = g.get('mcp_servers', {}) or {}
new_block = {}
missing = []
for name in declared:
if name in g_mcps:
new_block[name] = g_mcps[name]
else:
missing.append(name)
prev = set((p.get('mcp_servers') or {}).keys())
new = set(new_block.keys())
added = sorted(new - prev)
removed = sorted(prev - new)
p['mcp_servers'] = new_block
open(pcfg, 'w').write(yaml.safe_dump(p, sort_keys=False, allow_unicode=True))
for n in added: print(f" F6 + {n}")
for n in removed: print(f" F6 - {n} (denied)")
for n in missing: print(f" F6 ⚠ {n} (declared but not in global mcp_servers — skipped)")
print(f" F6 wrote mcp_servers: {len(new_block)} entr{'y' if len(new_block)==1 else 'ies'}")
PY
fi
else
echo " WARN: F6 yq/global config missing — skipping MCP materialization"
fi
echo ""
echo "== model policy → Codex primary + Qwen fallback =="
POLICY_SCRIPT="$(cd "$REPO/.." && pwd)/scripts/apply-hermes-model-policy.py"
if [ "$DRY" = 1 ]; then
echo "DRY: python3 '$POLICY_SCRIPT' --config '$PROFILE_CFG'"
elif [ -f "$POLICY_SCRIPT" ]; then
python3 "$POLICY_SCRIPT" --config "$PROFILE_CFG"
else
echo " WARN: policy script not found: $POLICY_SCRIPT"
fi
# F7 — macOS-only externals OS-gate (Wave 8 Q10)
# Reads expected_external_skills entries with os_constraint: darwin and emits
# an INFO line on non-Darwin hosts. No install action (these are external
# prereqs, not provisioned by this installer); annotation is the audit record.
HOST_OS="$(uname -s 2>/dev/null || echo Unknown)"
if [ "$DRY" = 1 ]; then
echo "DRY: F7 OS-gate check (host=$HOST_OS)"
elif command -v yq >/dev/null 2>&1; then
MACOS_ONLY=$(yq -r '.expected_external_skills[] | select(type == "!!map") | select(.os_constraint == "darwin") | .name' "$REPO/manifest.yaml" 2>/dev/null || true)
if [ -n "$MACOS_ONLY" ] && [ "$HOST_OS" != "Darwin" ]; then
echo " F7 INFO: macOS-only externals declared but host=$HOST_OS — unavailable:"
while IFS= read -r s; do [ -n "$s" ] && echo " - $s"; done <<< "$MACOS_ONLY"
fi
fi
echo ""
echo "== done =="
echo " verify skills: hermes -p steev skills list | grep steev-agent"
echo " verify mcp servers: hermes -p steev mcp list"
echo " verify assignee registered: hermes kanban assignees | grep steev"
echo " start gateway (when ready): hermes profile gateway start steev"

View File

@ -1,19 +1,250 @@
# Steev profile distribution manifest — machine-readable identity + install contract. # Steev profile distribution manifest — machine-readable identity + install contract.
# Read by install.sh. Convention shared by all profile distributions (see PROFILE-DISTRIBUTION-SPEC.md). # Read by install.sh. Convention shared by all Hermes profile distributions
profile: steev # (see ../sot/03-PROTOCOLS/PROFILE-DISTRIBUTION-PROTOCOL.md — the canonical protocol).
kind: profile-distribution profile: steev # Hermes profile name (personal — no org suffix per FRAMEWORK §6.1)
role: steev kind: profile-distribution # family marker; steev = personal-assistant reference impl
role: personal-assistant # function — Chief of Staff for one principal (JP)
# org: ~ # intentionally omitted — steev is personal/agnostic
version: 1.0.0 version: 1.0.0
identity: AGENT.md identity: AGENT.md # WHO (role, mission, boundaries)
reference: docs/STEEV-MASTER.md contract: CONTRACT.md # behavior contract — tier T1 (this file wins)
reference: docs/STEEV-MASTER.md # full operating source of truth
skills: # Governance — owner + vision + rules linked to SOT (PROFILE-DISTRIBUTION-PROTOCOL §2.2).
- skills/steev-agent # personal assistant orchestrator # Steev is JP-scoped personal; no brand_master_ref (not org-bound).
governance:
org: personal
owner: jp
approval_authority: jp
vision_refs:
- ../sot/01-ROADMAP/CORTEX-OS-ROADMAP.md
- ../sot/02-FRAMEWORK/CORTEX-OS-FRAMEWORK.md
governing_protocols:
- ../sot/03-PROTOCOLS/PROFILE-DISTRIBUTION-PROTOCOL.md
standards:
- ../sot/04-STANDARDS/FRONTMATTER-SPEC.md
- ../sot/04-STANDARDS/SOT-ENFORCEMENT.md
# brand_master_ref omitted — Steev serves JP personally, not a brand/org
north_star: "keep JP unblocked — surface what needs attention, draft in JP voice, delegate business work to CEO"
skills: # exposed to Hermes via skills.external_dirs (→ <repo>/skills)
- skills/steev-agent # orchestrator — daily briefing, inbox triage, comms drafting,
# business delegation to ceo-planb
- skills/proton-tools # Proton Calendar + Email + Contacts (24-tool reference) —
# uses the 3 cortex MCP servers (proton-calendar/-email/-contacts)
# Role tools = scripts at repo root (the "lib"), reached through credbridge.
# Personal-flow surface only; Plan B marketing CLIs out of scope (cmo-planb owns those).
lib:
- credbridge.sh # credctl → env → google-workspace / proton-bridge / perplexity
- validate_access.sh # PASS / BLOCKED / FAIL per credential per §7
# Hermes built-in / external skills Steev reuses but does NOT vendor (per CLAUDE.md
# "reuse existing core skills"). Informational — these come from Hermes' global skills
# tree (~/.hermes/skills/) or external skill libraries the principal already installed.
expected_external_skills:
- google-workspace # Gmail + Calendar + Contacts
- obsidian # ~/vaults/steev PKM
- himalaya # IMAP/SMTP via proton-bridge sidecar
- perplexity # WebSearch toolset (lightweight; MCP preferred)
# macOS-only skills (Wave 8 Q10): install.sh F7 emits info on non-Darwin hosts.
- name: apple-notes
os_constraint: darwin
- name: apple-reminders
os_constraint: darwin
- name: imessage
os_constraint: darwin
# MCP servers Steev consumes. Names match runtime-prefixed form (mcp_<server>_<tool>).
optional_tools:
- mcp_proton_calendar # 8-tool Proton Calendar facade
- mcp_proton_email # 10-tool Proton Email facade
- mcp_proton_contacts # 6-tool Proton Contacts facade
- mcp_perplexity # research / WebSearch (key held by MCP server, not credbridge)
requires_tools: [terminal, memory_tool] requires_tools: [terminal, memory_tool]
db: credentials: # validated by validate_access.sh
file: steev.db # runtime state; created from schema.sql; never committed # Wave 8 (2026-05-24): aligned with vault exact-match per DISCLOSURE-SCHEMA §4.5.
schema: schema.sql # google-workspace removed — builtin manages its own OAuth via Hermes hub (not credctl vault).
- name: proton-bridge-imap-user
purpose: Proton Bridge IMAP/SMTP username (himalaya path)
resolved_via: credbridge.sh
- name: proton-bridge-imap-pass
purpose: Proton Bridge IMAP/SMTP password (himalaya path)
resolved_via: credbridge.sh
- name: perplexity
purpose: Perplexity API key for raw WebSearch (MCP path preferred)
resolved_via: credbridge.sh
- name: proton-account-email
purpose: Proton account email (consumed by proton-email MCP server)
resolved_via: credbridge.sh
- name: proton-account-password
purpose: Proton account password (consumed by proton-email MCP server)
resolved_via: credbridge.sh
- name: proton-mailbox-password
purpose: Proton mailbox E2E key for mail decryption (consumed by proton-email MCP server)
resolved_via: credbridge.sh
cron: [] # daily-briefing cron registered disabled at launch db:
file: steev.db # runtime state; created from schema.sql; never committed
schema: schema.sql # briefings + inbox_items + invocations + agent_runtime
cron:
- id: steev-daily-briefing
schedule: "30 6 * * *" # 06:30 local — well before JP's start of day
skill: steev-agent
input: { mode: daily-briefing }
disabled_on_install: true # ships disabled per profile protocol §6 (Safety)
template: cron/steev-daily-briefing.json.template
sovereignty:
llm_model: qwen-local/qwen3.6-35b-a3b
host: dgx-spark
external_api_dependencies:
- perplexity # WebSearch only; build-time research path. Daily briefing scan uses 1-2 items.
# Disclosure block — runtime-truth contract per sot/04-STANDARDS/DISCLOSURE-SCHEMA.md.
# Wave-4 apply (2026-05-24). Closes Wave-1 audit findings:
# - HARD-RULE FIX: REMOVE bte MCP (Plan B marketing infra; CLAUDE.md:14 forbids
# access — steev is JP-personal-scope).
# - DENY 17 silently-inherited builtin skills (only kanban-worker kept for CEO
# delegation transport).
# - Personal-scope discriminator fields (scope/delegates_to) populated.
# Pre-push hook check 6 enforces this == live `hermes -p steev …` runtime.
disclosure:
scope: personal
schema_version: 2
delegates_to: [ceo-planb] # business work routed to CEO via kanban
inherit_builtins: false # deny Hermes 84-builtin default; allowlist below
inherit_mcp_toolsets: false # deny host MCP propagation (closes bte leak)
sovereign_only: false # perplexity (hosted) intentionally called for WebSearch
inherit_dirs: []
external_orchestrators: [] # steev has no exec'd orchestrators (no sandcastle equiv)
skills:
- id: steev-agent
source: local
path: skills/steev-agent
role: orchestrator
- id: proton-tools
source: local
path: skills/proton-tools
role: toolkit
justification: "24-tool Proton facade (Calendar+Email+Contacts) — JP-personal comms surface"
- id: assistant-identity
source: builtin
role: utility
justification: "live enabled Hermes profile skill surfaced by disclosure drift gate"
- id: proton-access
source: builtin
role: utility
justification: "live enabled Hermes profile skill surfaced by disclosure drift gate"
- id: proton-mail-operations
source: builtin
role: utility
justification: "live enabled Hermes profile skill surfaced by disclosure drift gate"
- id: proton-services
source: builtin
role: utility
justification: "live enabled Hermes profile skill surfaced by disclosure drift gate"
- id: google-workspace
source: builtin
path: productivity/google-workspace
role: engine
justification: "Gmail+Calendar+Contacts for daily briefing + inbox triage (manifest L46)"
- id: obsidian
source: builtin
path: note-taking/obsidian
role: engine
justification: "PKM vault at ~/vaults/steev (CLAUDE.md L17)"
- id: himalaya
source: builtin
path: email/himalaya
role: engine
justification: "IMAP/SMTP via proton-bridge (manifest L50)"
- id: kanban-worker
source: builtin
path: devops/kanban-worker
role: engine
justification: "CEO delegation transport — steev → ceo-planb (steev-agent SKILL.md L83)"
- id: webwright
source: builtin
role: utility
justification: "live enabled Hermes builtin surfaced by disclosure drift gate"
mcp_servers:
- name: proton-calendar
description: "Proton Calendar facade"
tools:
- calendar_list
- calendar_events
- calendar_upcoming
- calendar_search
- calendar_event_get
- calendar_create
- calendar_update
- calendar_delete
- name: proton-email
description: "Proton Email facade"
tools:
- email_folders
- email_list
- email_read
- email_search
- email_send
- email_reply
- email_forward
- email_archive
- email_mark_read
- email_mark_unread
- name: proton-contacts
description: "Proton Contacts facade"
tools:
- contacts_list
- contacts_search
- contacts_get
- contacts_create
- contacts_update
- contacts_delete
# DENY-BY-DEFAULT: bte removed (hard-rule fix).
# mcp_perplexity intentionally omitted from disclosure until it is
# registered in the live Hermes MCP list and can be introspected.
sovereign_apis: [] # 0 direct HTTP/gRPC calls (per audit §3)
cortex_tools: [] # steev does not consume cortex/L6-* or cortex/PG-*
credentials:
# Wave 8 (2026-05-24) — aligned with vault per DISCLOSURE-SCHEMA §4.5 (exact-match).
# google-workspace removed (Hermes builtin self-manages OAuth, not in credctl vault).
- vault_name: proton-bridge-imap-user
status: required
scope: read
used_by: [credbridge.sh]
governance: "JP-personal; local Proton Bridge IMAP/SMTP username (himalaya path)"
- vault_name: proton-bridge-imap-pass
status: required
scope: read
used_by: [credbridge.sh]
governance: "JP-personal; local Proton Bridge IMAP/SMTP password (himalaya path)"
- vault_name: perplexity
status: optional
scope: read
used_by: [credbridge.sh]
governance: "JP-personal; WebSearch fallback (MCP path preferred)"
- vault_name: proton-account-email
status: required
scope: read
used_by: [credbridge.sh, mcp_proton_email]
governance: "JP-personal; Proton account email (consumed by proton-email MCP server)"
- vault_name: proton-account-password
status: required
scope: read
used_by: [credbridge.sh, mcp_proton_email]
governance: "JP-personal; Proton account password (consumed by proton-email MCP server)"
- vault_name: proton-mailbox-password
status: required
scope: read
used_by: [credbridge.sh, mcp_proton_email]
governance: "JP-personal; Proton mailbox E2E key for mail decryption"

View File

@ -0,0 +1,92 @@
---
name: proton-tools
description: "When Steev needs to access JP's Proton account — Calendar, Mail, Contacts, or explicitly requested Proton Drive checks via rclone. Use this skill to discover which tool answers the user's question, and how to call it. Covers all 24 Proton MCP tools across the three cortex MCP servers (proton-calendar, proton-email, proton-contacts). Triggers: any request involving JP's calendar (events, meetings, availability), mail (inbox, send, reply, search, folders), contacts (lookup, add, search), or Drive via rclone."
metadata:
version: 1.0.0
hermes:
requires_mcp_servers: [proton-calendar, proton-email, proton-contacts]
---
# Proton Tools — Calendar + Mail + Contacts
Authoritative reference for the 24 tools exposed by three cortex MCP servers — `proton-calendar` (8 tools), `proton-email` (10 tools), `proton-contacts` (6 tools). Each MCP facade dials a long-running gRPC gate that holds the Proton session.
## Hard rules
- **Drive is out of scope for Proton MCP tools.** There is no `drive_*` MCP tool. If the user explicitly asks to check Drive via `rclone`, use the live Proton Drive rclone remote instead of claiming no access: this Steev/Hermes profile sets `HOME=/home/svrnty/.hermes/profiles/steev/home`, so plain `rclone` sees the profile config; the working Proton Drive config is `/home/svrnty/.config/rclone/rclone.conf` with remote `proton:`. Use read-only probes first (`rclone --config /home/svrnty/.config/rclone/rclone.conf about proton: --json`) and do not list file names unless JP asks.
- **Destructive tools require explicit confirmation.** `email_send`, `email_reply`, `email_forward`, `calendar_delete`, `contacts_delete`. Never call these without quoting back the action + target + asking JP to confirm.
- **Calendar date filters:** the MCP schema may advertise RFC3339, but `calendar_events`/underlying gate expects date-only filters (`YYYY-MM-DD`) for reliable results. RFC3339 ranges can return empty even when events exist. Convert relative dates ("tomorrow", "next Tuesday") into `YYYY-MM-DD` for list/search filters; keep event create/update timestamps RFC3339.
- **Pagination**: `email_list`, `calendar_events`, `contacts_list` are paginated. Default page size is small (~20). Fetch additional pages only when the user asks for more.
## When to use which tool
### Calendar (8 tools)
| User intent | Tool |
|---|---|
| "What calendars do I have?" | `calendar_list` |
| "What's on my calendar today/this week?" | `calendar_events` with date range |
| "What's coming up?" "Next few meetings?" | `calendar_upcoming` |
| "Find meetings about X" | `calendar_search` |
| "Show me details of [event]" | `calendar_event_get` |
| "Schedule a meeting with…" | `calendar_create` (confirm first) |
| "Move my 3pm to 4pm" | `calendar_update` |
| "Cancel my 3pm" | `calendar_delete` (DESTRUCTIVE — confirm) |
### Mail (10 tools)
| User intent | Tool |
|---|---|
| "How many unread?" "What folders?" | `email_folders` |
| "Show me my inbox" "Latest emails" | `email_list` (folder=INBOX) |
| "Open that email" | `email_read` by UID |
| "Search inbox for…" | `email_search` |
| "Send an email to…" | `email_send` (DESTRUCTIVE — draft + confirm) |
| "Reply to that" | `email_reply` (DESTRUCTIVE — draft + confirm) |
| "Forward this to…" | `email_forward` (DESTRUCTIVE — confirm) |
| "Archive that" | `email_archive` |
| "Mark as read/unread" | `email_mark_read` / `email_mark_unread` |
### Contacts (6 tools)
| User intent | Tool |
|---|---|
| "Who do I have in contacts?" | `contacts_list` |
| "Look up [person]" | `contacts_search` |
| "Pull up [person]'s details" | `contacts_get` |
| "Add [person] to contacts" | `contacts_create` |
| "Update [person]'s email/phone" | `contacts_update` |
| "Remove [person]" | `contacts_delete` (DESTRUCTIVE — confirm) |
## Daily briefing — tool order
When JP asks for the morning briefing, query in this order:
1. `calendar_upcoming` (hours=24) → events today
2. `email_folders` → unread counts
3. `email_list` (folder=INBOX, limit=10) → recent inbox
4. `email_search` (folder=INBOX, query="from:important-person OR is:flagged") → priorities
Don't dump raw output. Synthesize. Lead with what's actionable in JP's voice.
## Search composition
For broad questions like "anything from [person] this week":
- `email_search` (folder=INBOX, query="from:<person>")
- `calendar_search` (query="<person>")
- `contacts_search` (query="<person>")
Run in parallel. Merge results. Group by source.
## Error handling
- **"WaitReady timeout"** → proton connector still booting. Retry once after 2-3s. If still failing, say so + suggest JP check `hermes mcp test proton`.
- **403 / scope error** → proton session expired. Tool handler should re-auth automatically; if not, JP needs to re-run setup.
- **Network / 5xx** → transient. Retry once. If persistent, report and stop.
- **`calendar_create` timeout** → do not retry blindly. First verify the target date range with `calendar_events` using `YYYY-MM-DD` filters to avoid duplicate events. If the event is still absent, one direct gate fallback may be attempted. If creates keep timing out while reads work, refresh `sdo-calendar-gate`: `docker restart sdo-calendar-gate`, wait for `connected to Proton` + `calendar-gate gRPC server listening`, then retry once. If restart fails with a bind-mount error because `/home/svrnty/workspaces/cortex/svrnty.sdo-agents/config/calendar-gate.toml` is a directory, replace it with a symlink to `../../L3-svrnty.agents-fleet/config/calendar-gate.toml`, then `docker start sdo-calendar-gate`.
## What NOT to do
- Don't paginate aggressively — fetch one page, summarize, ask if JP wants more.
- Don't auto-send drafts. Even after JP says "send" once, re-quote subject + recipient on the next compose.
- Don't synthesize calendar events from email content unless JP explicitly asks ("add this to my calendar").
- Don't enumerate every contact when JP asks "who's [person]" — use search, not list.

View File

@ -0,0 +1,36 @@
# JP Voice Card — Stub
> **STATUS:** Stub placeholder. Replace with JP voice samples once JP provides them. Per `CONTRACT.md §5`, Steev drafts in JP's voice — this card defines what that voice is so drafts are consistent.
## Defaults (until JP-curated samples land)
- **Tone:** direct, warm, bilingual (French/English; switch is contextual, not random — French for Québec personal, English for technical/business when default).
- **Cadence:** short sentences. Fragments OK. Action-oriented.
- **Pronouns:** "I" / "we" (Plan B contexts) / informal "tu" in French personal contexts, formal "vous" in business French.
- **Filler ban:** no "I hope this email finds you well", no "as per", no "please be advised". Cold open: state the ask or the answer.
- **Honesty:** if uncertain, name uncertainty. If declining, decline clearly + give the real reason.
## Triggers requiring voice card upgrade
- First drafted comm rejected by JP with voice-mismatch correction → capture the corrected sentence here under `## Curated samples`.
- JP provides existing email/message corpus → extract patterns, replace defaults above.
## Curated samples (empty — populate as JP provides)
<!-- format:
**Context:** [briefing | inbox-reply | comms-draft | delegation-brief | personal-note]
**Original:** [Steev's draft]
**JP's version:** [what JP actually sent]
**Pattern:** [what to extract — e.g., "drops 'I think' before opinions"]
-->
## Anti-patterns (Plan B brand voice ≠ JP voice)
- Plan B marketing voice = warm, French-Québec, family-meal-centric → **CMO's domain, not Steev's**
- Corporate-bot voice ("Best regards", "Kind regards", "I trust this email finds you well") → never
- Over-hedging ("perhaps", "maybe we could consider") → JP commits or asks, doesn't hedge
## See also
- `SKILL.md §3` — voice card load order
- `../../CONTRACT.md §5` — Steev voice scope (JP only, not Plan B brand)

35
tools/validate_steev_child.py Executable file
View File

@ -0,0 +1,35 @@
#!/usr/bin/env python3
"""Validate Steev Profile child workspace shell."""
from __future__ import annotations
import json
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
REQUIRED = ["AGENTS.md", "README.md", "WORKBOARD.yaml"]
def main() -> int:
errors: list[str] = []
for rel in REQUIRED:
if not (ROOT / rel).exists():
errors.append(f"missing:{rel}")
board = ROOT / "WORKBOARD.yaml"
if board.exists():
text = board.read_text(encoding="utf-8")
for snippet in ["STEEV-WORK-001", "status: candidate", "owner: jp"]:
if snippet not in text:
errors.append(f"workboard_missing:{snippet}")
agents = ROOT / "AGENTS.md"
if agents.exists():
text = agents.read_text(encoding="utf-8")
for snippet in ["child-local", "not Cortex OS Core authority", "python3 tools/validate_steev_child.py"]:
if snippet not in text:
errors.append(f"agents_missing:{snippet}")
result = {"ok": not errors, "validator": "steev-child-v1", "checked": REQUIRED, "errors": errors, "warnings": []}
print(json.dumps(result, indent=2, sort_keys=True))
return 0 if result["ok"] else 1
if __name__ == "__main__":
raise SystemExit(main())

42
validate_access.sh Executable file
View File

@ -0,0 +1,42 @@
#!/usr/bin/env bash
# validate_access.sh — report PASS / BLOCKED / FAIL per credential, per
# PROFILE-DISTRIBUTION-PROTOCOL §7 (readiness checklist, "credbridge resolves
# every credential the manifest lists; validate_access reports PASS/BLOCKED/
# FAIL"). Sourceable from install.sh and standalone.
#
# Usage: validate_access.sh
# Exit code: always 0. Emits one JSON line per credential, suitable for jq /
# log aggregation.
#
# Statuses:
# PASS credctl key set + non-empty
# BLOCKED key absent or empty — actionable: run `credctl set <name>`
# FAIL credctl itself missing or broken — environmental issue
set -uo pipefail
CREDCTL="${CREDCTL:-/home/svrnty/workspaces/cortex/L6-svrnty.core-credentials/credctl}"
CREDENTIALS=(
google-workspace
proton-bridge-imap
perplexity-api
)
check() {
local name="$1" status reason
if [ ! -x "$CREDCTL" ]; then
status="FAIL"; reason="credctl not found at $CREDCTL"
elif ! "$CREDCTL" list 2>/dev/null | grep -q "^${name}[[:space:]]"; then
status="BLOCKED"; reason="credctl key not set — run: credctl set ${name}"
elif [ -z "$("$CREDCTL" get "$name" --unmask 2>/dev/null | sed -n '/^Value:/,$p' | sed '1s/^Value:[[:space:]]*//')" ]; then
status="BLOCKED"; reason="key exists but value empty"
else
status="PASS"; reason="present"
fi
printf '{"credential":"%s","status":"%s","reason":"%s"}\n' "$name" "$status" "$reason"
}
for cred in "${CREDENTIALS[@]}"; do
check "$cred"
done