Compare commits
17 Commits
a403c733fd
...
aeb17cce22
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aeb17cce22 | ||
|
|
c7b72a8758 | ||
|
|
0487a3d8fd | ||
|
|
fdc27aa92f | ||
|
|
2491d48151 | ||
|
|
959b8c8871 | ||
|
|
57ef5411a4 | ||
|
|
30d586e79e | ||
|
|
6f5ca6573c | ||
|
|
b85b266dcb | ||
|
|
8e8ced470b | ||
|
|
ff2b88a088 | ||
|
|
7ea62147a6 | ||
|
|
2db2d26250 | ||
|
|
66c742c219 | ||
|
|
0f5e807015 | ||
|
|
fdd434c559 |
8
.env.example
Normal file
8
.env.example
Normal 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"
|
||||
13
AGENT.md
13
AGENT.md
@ -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
|
||||
|
||||
> 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
32
AGENTS.md
Normal 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
117
CLAUDE.md
@ -1,87 +1,58 @@
|
||||
# Working Principles
|
||||
|
||||
## 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
|
||||
# steev
|
||||
|
||||
**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)
|
||||
- Business tasks → delegate to CEO, never execute directly
|
||||
- No access to Plan B marketing platform credentials
|
||||
- Steev drafts in JP's voice — NEVER in Plan B brand voice (that's CMO's domain)
|
||||
- Business tasks → delegate to CEO via kanban, never execute directly
|
||||
- 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)
|
||||
- `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
|
||||
|
||||
```
|
||||
steev/
|
||||
├── manifest.yaml
|
||||
├── AGENT.md
|
||||
├── CLAUDE.md
|
||||
├── install.sh
|
||||
├── skills/steev-agent/
|
||||
│ └── SKILL.md
|
||||
├── schema.sql
|
||||
└── docs/
|
||||
└── STEEV-MASTER.md
|
||||
├── manifest.yaml # profile: steev, kind: profile-distribution
|
||||
├── AGENT.md # Steev identity
|
||||
├── install.sh # idempotent installer → ~/.hermes/steev symlink
|
||||
├── skills/
|
||||
│ ├── steev-agent/ # orchestrator skill (SKILL.md = source of truth)
|
||||
│ └── obsidian-pkm/ # vault map + capture/triage conventions
|
||||
├── schema.sql # steev.db schema
|
||||
└── CONTRACT.md # Steev profile contract — tier S (this file wins)
|
||||
```
|
||||
|
||||
## 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.
|
||||
|
||||
@ -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
|
||||
|
||||
**Role:** Personal Assistant / Chief of Staff for JP (Mathias)
|
||||
153
DISCLOSURE.md
Normal file
153
DISCLOSURE.md
Normal 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
6
WORKBOARD.yaml
Normal 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
81
credbridge.sh
Executable 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
|
||||
40
cron/steev-daily-briefing.json.template
Normal file
40
cron/steev-daily-briefing.json.template
Normal 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
29
distribution.yaml
Normal 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/
|
||||
335
install.sh
335
install.sh
@ -56,4 +56,337 @@ fi
|
||||
echo "== steev.db =="
|
||||
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"
|
||||
|
||||
255
manifest.yaml
255
manifest.yaml
@ -1,19 +1,250 @@
|
||||
# Steev profile distribution manifest — machine-readable identity + install contract.
|
||||
# Read by install.sh. Convention shared by all profile distributions (see PROFILE-DISTRIBUTION-SPEC.md).
|
||||
profile: steev
|
||||
kind: profile-distribution
|
||||
role: steev
|
||||
# Read by install.sh. Convention shared by all Hermes profile distributions
|
||||
# (see ../sot/03-PROTOCOLS/PROFILE-DISTRIBUTION-PROTOCOL.md — the canonical protocol).
|
||||
profile: steev # Hermes profile name (personal — no org suffix per FRAMEWORK §6.1)
|
||||
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
|
||||
identity: AGENT.md
|
||||
reference: docs/STEEV-MASTER.md
|
||||
identity: AGENT.md # WHO (role, mission, boundaries)
|
||||
contract: CONTRACT.md # behavior contract — tier T1 (this file wins)
|
||||
reference: docs/STEEV-MASTER.md # full operating source of truth
|
||||
|
||||
skills:
|
||||
- skills/steev-agent # personal assistant orchestrator
|
||||
# Governance — owner + vision + rules linked to SOT (PROFILE-DISTRIBUTION-PROTOCOL §2.2).
|
||||
# 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]
|
||||
|
||||
db:
|
||||
file: steev.db # runtime state; created from schema.sql; never committed
|
||||
schema: schema.sql
|
||||
credentials: # validated by validate_access.sh
|
||||
# Wave 8 (2026-05-24): aligned with vault exact-match per DISCLOSURE-SCHEMA §4.5.
|
||||
# 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"
|
||||
|
||||
92
skills/proton-tools/SKILL.md
Normal file
92
skills/proton-tools/SKILL.md
Normal 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.
|
||||
36
skills/steev-agent/jp-voice.md
Normal file
36
skills/steev-agent/jp-voice.md
Normal 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
35
tools/validate_steev_child.py
Executable 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
42
validate_access.sh
Executable 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
|
||||
Loading…
Reference in New Issue
Block a user