herdr workforce output (6 codex angle-specialists -> claude synthesis): S1 ledger/economy, S2 catalog/registry, S3 storefront (cargstore->M2 Store), S4 Solution Scout (standalone watcher recommended), S5 packaging + 3 seed Solutions (mm-pdf 25cr, agent-scaffold 35cr, competitor-scan 60cr), S6 proposal engine + evidence loop. SPEC-INDEX is the tie-break: 23-row cross-cutting contract table (16 mismatches resolved, e.g. /v1 ledger paths win, m2store://listing/<id> deep link, one m2.market.telemetry.v1 envelope, op_<slug> operator ids, tenant ids from m2-gpt), 8 deduped clarifications with defaults, 4-phase build order where the paid-install wedge does NOT block on fedlearn (local ApplyAdapter until rails land), risk register. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
21 KiB
S4 — Solution Scout Spec
1. Scope & non-goals
MVP scope
Solution Scout v0 is a per-desktop, propose-only service that notices when an operator is already working on a task that a published M2 Marketplace listing may cover, then surfaces a non-blocking proposal.
Recommended host: standalone supervised watcher inside each desktop container, deployed canary-first on chris-m2o, then one agent-latest canary such as gunnar-m2o. It reads only already-structured local summaries, writes only proposal telemetry, and calls the existing catalog/store/install contracts.
MVP responsibilities:
- Watch safe input taps:
- herdr run summaries from
~/.herdr/runs/*.mdand herdr session metadata from~/.config/herdr/session.jsonwhen present. - Hermes/OpenClaw session summaries only if the harness writes an explicit summary file or emits a summary event. No raw chat logs unless another subsystem has already summarized and scrubbed them.
- Optional window title summaries only when explicitly enabled in policy; default off.
- herdr run summaries from
- Summarize on-box before any external call. The Scout sends a short intent summary, not raw workspace/client text.
- Query the semantic catalog through memory-api using partition/agent namespace
market:catalog, tenant-filtered by the caller tenant. - Decide whether to propose using confidence, coverage, recency, rate-limit, dismissal cooldown, and policy thresholds.
- Surface proposals as:
- herdr/XFCE toast with a deep link into M2 Store.
- Hermes chat surfacing when Hermes is active and exposes the market-propose bridge.
- Persist local proposal state and write accept/dismiss/show telemetry to
market:evidence. - Respect opt-in config in
~/.config/m2-market/pull-policy.toml.
Later scope
- Full Hermes-native agent reasoning for build-vs-install proposals belongs to S6. S4 only hands Hermes a candidate proposal event.
- Automatic install is out of scope. Human clicks through M2 Store and
m2-market installhandles debit/grant/apply. - Raw keystroke capture, screen recording OCR, shell history capture, browser DOM scraping, and unreviewed client data extraction are explicitly out of scope.
- Fleet-wide rollout, packaging format, catalog truth, storefront implementation, and ledger internals are owned by S5/S2/S3/S1 respectively.
2. User stories + acceptance criteria
Story 1 — Opt-in canary proposal
Given chris-m2o has ~/.config/m2-market/pull-policy.toml with scout.enabled = true
and a herdr summary describes a competitor research report,
when Scout runs its poll loop,
then it creates an on-box intent summary, queries market:catalog within the machine tenant, and shows at most one proposal for competitor-scan if score and coverage exceed thresholds.
Acceptance:
- The payload sent to memory-api contains no raw run body longer than
summary.max_chars. - The proposal contains listing name, price, confidence/coverage, install count/rating if present, and Store deep link.
~/.local/share/m2-market/scout/state.jsonrecords the proposal id and cooldown.
Story 2 — Human-controlled install path
Given a proposal toast is shown,
when the operator clicks Open,
then M2 Store opens m2store://listing/<listing_id>?proposal_id=<proposal_id>&source=scout.
Acceptance:
- Scout never calls
m2-market installdirectly. - Store/CLI remain responsible for ledger
/tx/install, license grant, bundle verification, apply adapter, and refund on failure. - Scout observes accept/installed telemetry only after Store/CLI emits an explicit event.
Story 3 — Dismissal tunes future proposals
Given an operator dismisses a proposal, when the same listing/task pair appears again within the configured cooldown, then Scout suppresses the proposal and writes a dismiss event to evidence.
Acceptance:
- Dismissal is keyed by
{tenant_id, operator_id, desktop_id, listing_id, intent_hash}. - Dismiss telemetry is stored with a summary hash and reason, not client content.
- Listing evidence can fold
proposals_shownandproposals_acceptedintolisting.stats.
Story 4 — Tenant firewall
Given the desktop belongs to sdjs/GST or another client tenant,
when Scout queries the catalog,
then only listings with tenant_visibility containing that tenant or "*" are eligible.
Acceptance:
- Empty tenant filter is a hard error, not an unscoped search.
- Tenant-private summaries never become shared catalog data.
- Owner-initiated commercialization and double scrub remain packaging/catalog obligations, not Scout behavior.
3. Interfaces & data contracts
Process and deployment
Process name: m2-solution-scout
MVP placement:
- Desktop binary:
/usr/local/bin/m2-solution-scout - Supervisor config:
/etc/supervisor/conf.d/m2-solution-scout.conf - User config:
~/.config/m2-market/pull-policy.toml - Local state:
~/.local/share/m2-market/scout/state.json - Local event outbox:
~/.local/share/m2-market/scout/outbox/YYYY-MM-DD.jsonl - Logs:
~/.local/share/m2-market/scout/scout.log
Deploy via runtime-sync / apply adapter, not image bake, until canary acceptance proves it. The service must tolerate missing herdr/Hermes/OpenClaw sources and run as degraded rather than crash-looping.
pull-policy.toml
[scout]
enabled = false
mode = "suggest" # enum: off|suggest. No take-control mode in S4.
desktop_id = "chris-m2o"
operator_id = "op_chris"
tenant_id = "machine-machine"
min_score = 0.78
min_coverage = 0.55
poll_interval_seconds = 90
max_proposals_per_day = 4
max_proposals_per_session = 1
dismiss_cooldown_days = 14
proposal_cooldown_minutes = 45
[scout.sources]
herdr_runs = true
session_summaries = true
window_titles = false
raw_keystrokes = false
raw_client_data = false
[scout.summary]
max_source_chars = 12000
max_summary_chars = 900
redact_secrets = true
redact_client_identifiers = true
fail_closed_on_secret = true
[scout.catalog]
memory_api_url = "https://memory.machinemachine.ai"
agent_id = "market:catalog"
limit = 5
routing_strategy = "standard"
[scout.store]
deeplink_scheme = "m2store"
fallback_command = "m2-market show --json"
[scout.telemetry]
enabled = true
agent_id = "market:evidence"
batch_seconds = 60
Policy rules:
enabled=falsedisables all watching and all outbound calls.tenant_idis required. If missing, Scout exits non-zero and writes no events.raw_keystrokesandraw_client_dataare reserved deny keys. If true, Scout refuses to start.- All keys are runtime-injected/configured. No secrets in this file except references to existing machine config; memory/ledger keys remain in
~/.m2-market/config.tomlwith mode0600.
Input event schema
Scout normalizes safe local taps into ScoutObservation.
{
"schema_version": "m2.scout.observation.v1",
"observation_id": "obs_20260702_chris_01j1",
"desktop_id": "chris-m2o",
"operator_id": "op_chris",
"tenant_id": "machine-machine",
"source": "herdr_run",
"source_uri": "file:///home/developer/.herdr/runs/2026-07-02-fedlearn-seed.md",
"source_ts": "2026-07-02T00:15:00Z",
"session_id": "herdr:2026-07-02-fedlearn-seed",
"safe_excerpt": "Short scrubbed excerpt or heading only",
"source_hash": "sha256:...",
"metadata": {
"image_class": "primus",
"harness": "herdr"
}
}
Constraints:
safe_excerptis optional and capped by policy; raw source body is never persisted in telemetry.source_hashis computed from local source text for dedup, but only the hash leaves the box.- For herdr MVP, source files are the live path verified on
chris-m2o:~/.herdr/runs/*.md; config/session files are under~/.config/herdr/.
On-box intent summary schema
{
"schema_version": "m2.scout.intent_summary.v1",
"summary_id": "sum_20260702_chris_01j1",
"observation_ids": ["obs_20260702_chris_01j1"],
"desktop_id": "chris-m2o",
"operator_id": "op_chris",
"tenant_id": "machine-machine",
"session_id": "herdr:2026-07-02-fedlearn-seed",
"summary": "Operator is preparing a competitor research report with source gathering and final PDF/report output.",
"intent_terms": ["competitor research", "report", "sources", "PDF"],
"negative_terms": ["private payroll", "credentials"],
"scrub_status": {
"secrets_redacted": true,
"client_identifiers_redacted": true,
"failed_closed": false
},
"intent_hash": "sha256:...",
"created_at": "2026-07-02T00:35:00Z"
}
Summarization can be deterministic extractive in MVP. If an LLM is used later, it must call m2-gpt with the desktop tenant key and still send only the capped summary onward.
Catalog query
Use existing memory-api /memory/search surface rather than a new Scout API.
Request:
POST https://memory.machinemachine.ai/memory/search
X-API-Key: <memory api key from ~/.m2-market/config.toml>
Content-Type: application/json
{
"query": "Operator is preparing a competitor research report with source gathering and final PDF/report output.",
"agent_id": "market:catalog",
"routing_strategy": "standard",
"limit": 5,
"tenant_id": ["machine-machine", "*"],
"filters": {
"tenant_id": ["machine-machine", "*"]
}
}
Response is the live RoutedSearchResponse shape from memory-api: {results, routing}. Each result must carry listing payload metadata matching schemas/listing.schema.json. The matcher discards any hit whose listing payload is missing, delisted, wrong tenant, or below threshold.
Match decision schema
{
"schema_version": "m2.scout.match.v1",
"match_id": "mat_20260702_chris_01j1",
"summary_id": "sum_20260702_chris_01j1",
"listing_id": "lst_competitor-scan",
"solution_id": "sol_competitor-scan",
"tenant_id": "machine-machine",
"score": 0.84,
"coverage": 0.72,
"reasons": [
"intent overlaps listing intent",
"listing supports report generation",
"price/evidence present"
],
"suppressed": false,
"suppression_reason": null,
"created_at": "2026-07-02T00:35:03Z"
}
Coverage is Scout's local estimate of how much of the current intent is covered by the listing. It is not authoritative pricing. S6 owns full build-vs-install composition.
Proposal schema
{
"schema_version": "m2.scout.proposal.v1",
"proposal_id": "prp_20260702_chris_01j1",
"match_id": "mat_20260702_chris_01j1",
"desktop_id": "chris-m2o",
"operator_id": "op_chris",
"tenant_id": "machine-machine",
"listing": {
"listing_id": "lst_competitor-scan",
"name": "Competitor Scan",
"summary": "Research report package for competitor positioning.",
"price": {"amount": 40, "currency": "m2cr", "model": "fixed"},
"stats": {"installs": 12, "rating": 4.6, "proposals_shown": 20, "proposals_accepted": 7}
},
"confidence": 0.84,
"coverage": 0.72,
"deeplink": "m2store://listing/lst_competitor-scan?proposal_id=prp_20260702_chris_01j1&source=scout",
"shown_at": "2026-07-02T00:35:04Z",
"expires_at": "2026-07-02T01:20:04Z"
}
Toast copy should be non-blocking and specific:
This looks like Competitor Scan: ~72% coverage, 40 m2cr. Open in M2 Store?
Proposal UX interfaces
MVP toast command:
notify-send "M2 Solution Scout" "This looks like Competitor Scan: ~72% coverage, 40 m2cr. Open in M2 Store?" --action=open="Open" --action=dismiss="Dismiss"
If notify-send actions are unreliable in XFCE/VNC, fallback to herdr toast delivery. The existing herdr config supports [ui.toast] delivery = "herdr", verified in /home/m2/m2o/desktop/herdr/config.toml.
M2 Store deep link:
m2store://listing/{listing_id}?proposal_id={proposal_id}&source=scout
Store fallback command:
m2-market show {listing_id} --json
Hermes surfacing event, written to a local bridge file/socket for the market-propose skill:
{
"schema_version": "m2.hermes.market_proposal.v1",
"event_type": "market.proposal.available",
"proposal_id": "prp_20260702_chris_01j1",
"session_id": "herdr:2026-07-02-fedlearn-seed",
"message": "I found an existing Solution that may cover this task.",
"deeplink": "m2store://listing/lst_competitor-scan?proposal_id=prp_20260702_chris_01j1&source=scout"
}
The Hermes bridge is best-effort in MVP. If unavailable, toast + Store link is sufficient.
Telemetry event schema
Telemetry is append-only, locally spooled, then written to memory-api market:evidence via /memory/store.
{
"schema_version": "m2.scout.telemetry.v1",
"event_id": "evt_20260702_chris_01j1",
"event_type": "proposal_shown",
"proposal_id": "prp_20260702_chris_01j1",
"listing_id": "lst_competitor-scan",
"solution_id": "sol_competitor-scan",
"operator_id": "op_chris",
"desktop_id": "chris-m2o",
"tenant_id": "machine-machine",
"session_id": "herdr:2026-07-02-fedlearn-seed",
"intent_hash": "sha256:...",
"score": 0.84,
"coverage": 0.72,
"reason": null,
"created_at": "2026-07-02T00:35:04Z"
}
Allowed event_type:
proposal_shownproposal_openedproposal_dismissedproposal_acceptedinstall_startedinstall_succeededinstall_failedproposal_suppressed
Memory write:
POST https://memory.machinemachine.ai/memory/store
X-API-Key: <memory api key>
Content-Type: application/json
{
"content": "proposal_shown lst_competitor-scan score=0.84 coverage=0.72",
"agent_id": "market:evidence",
"memory_type": "semantic",
"importance": 0.4,
"source": "system",
"tenant_id": "machine-machine",
"session_id": "herdr:2026-07-02-fedlearn-seed",
"metadata": {
"schema_version": "m2.scout.telemetry.v1",
"event_id": "evt_20260702_chris_01j1",
"event_type": "proposal_shown",
"listing_id": "lst_competitor-scan",
"proposal_id": "prp_20260702_chris_01j1",
"intent_hash": "sha256:..."
}
}
Telemetry must be batchable and retryable. If memory-api is down, Scout keeps local JSONL outbox and continues proposing within local rate limits.
CLI verbs
Scout management CLI:
m2-solution-scout run
m2-solution-scout once --json
m2-solution-scout status --json
m2-solution-scout dismiss <proposal_id> [--reason not-relevant]
m2-solution-scout open <proposal_id>
m2-solution-scout flush-telemetry
once --json is the canary acceptance hook: read sources, summarize, query, and emit a proposal or suppression decision without daemonizing.
4. Integration contract with other subsystems
S1 ledger
Scout does not debit credits. It includes listing price in the proposal and links to Store. Install remains:
M2 Store -> m2-market install -> POST /tx/install -> license grant -> apply -> telemetry
Scout listens for Store/CLI install result events and writes evidence only. Refund behavior remains S1/S3/CLI.
S2 catalog
Scout reads market:catalog only through memory-api /memory/search using agent_id = "market:catalog". Listing payload must validate against /home/m2/m2-market/schemas/listing.schema.json.
Scout writes proposal evidence to market:evidence. S2/indexer folds proposal counts into listing.stats.proposals_shown and listing.stats.proposals_accepted.
S3 store
Scout proposal opens:
m2store://listing/{listing_id}?proposal_id={proposal_id}&source=scout
Store shows evidence, price, permission diff, and calls m2-market install. Store should emit local result events:
~/.local/share/m2-market/events/install-events.jsonl, or- a local socket declared by S3.
Until S3 finalizes this, Scout can infer only proposal_opened and proposal_dismissed.
S5 packaging
Scout is installed as a Solution/fleet component via the same runtime-sync path and canary policy. It must not require rebuilding primus or agent-latest images. Package should include:
- binary/script
- supervisor conf
- default disabled
pull-policy.toml - rollback removing supervisor entry and disabling the daemon
S6 proposal engine
Scout is push-side discovery. Hermes/S6 is pull-side composition. Scout hands candidate listing evidence to Hermes but does not produce full alternatives such as install+adapt vs operator-assisted vs full custom.
S6 may consume m2.hermes.market_proposal.v1 and market:evidence; it must preserve Scout's privacy boundary.
Existing live system touchpoints
- Desktop canary:
chris-m2ois running oncoolify, has/usr/local/bin/herdr,~/.herdr/runs/*.md,~/.config/herdr/, and supervisedhermes-gateway. - Agent-latest canaries exist on
coolify, includinggunnar-m2o-...,nasr-m2o-...,parlobyg-m2o-..., andpeter-desktop-.... - memory-api public health is live at
https://memory.machinemachine.ai/health; internal desktop alias ishttp://memory-api:8000after fedlearn resolver hardening. - m2-gpt public health is live at
https://gpt.machinemachine.ai/health; Hermes config points athttps://gpt.machinemachine.ai/v1withM2_GPT_API_KEYinjected at runtime. - New services/components must be canary-first, idempotent, reversible, and use Coolify/runtime envs for secrets.
5. Options compared
Option A — Standalone supervised watcher
What it is: a small daemon/process on each desktop, managed like hermes-gateway, reading safe local summary files/events and calling catalog APIs directly.
Pros:
- Harness-agnostic: works for Hermes, herdr, OpenClaw, and future OpenAI-wire clients.
- Least coupling to harness release cycles and plugin APIs.
- Clear privacy boundary: only the watcher owns taps, summaries, policy, and telemetry.
- Can be deployed by runtime-sync to mixed primus/agent-latest images.
- Failure is isolated; if Scout breaks, Hermes and herdr keep working.
Cons:
- Needs its own supervisor config, local state, and policy file.
- Less direct access to Hermes intent than a Hermes plugin.
- Needs a small bridge to surface in Hermes chat.
Option B — Hermes plugin
What it is: implement Scout inside Hermes or as a Hermes skill/plugin.
Pros:
- Sees user intent directly in the primary conversational surface.
- Natural chat UX for explaining build-vs-install options.
- Reuses m2-gpt identity and model path.
Cons:
- Hermes-specific; OpenClaw and non-Hermes OpenAI-wire clients lose push discovery.
- Higher surveillance risk because it is closer to raw chat/tool context.
- Plugin failures can degrade the primary agent experience.
- herdr run lifecycle signals still need a separate bridge.
Option C — herdr plugin
What it is: implement Scout as a herdr extension using herdr lifecycle events and run summaries.
Pros:
- Strongest access to structured lifecycle/run context.
- Aligns with live
~/.herdr/runs/*.mdcapture source and toast delivery. - Lowest risk of raw chat access if limited to run summaries.
Cons:
- Misses Hermes-only or OpenClaw-only sessions that do not run through herdr.
- Couples marketplace discovery to herdr's extension surface and lifecycle.
- Still needs Hermes and Store bridges.
Recommendation
Choose Option A: standalone supervised watcher for MVP.
Reason: S4's hard requirements are privacy, tenant isolation, mixed-fleet installability, and harness-agnostic discovery. A standalone watcher best satisfies those while still consuming herdr summaries and surfacing through Hermes. Hermes and herdr should be integrations, not the host boundary.
6. Risks/edge cases
- Surveillance creep: prohibit raw keystrokes/client data in config and startup validation; default disabled; cap source and summary sizes; make summary/outbound payload inspectable with
once --json. - Tenant leakage: require tenant_id and use tenant-filtered catalog queries. Empty tenant filters fail closed. Telemetry stores intent hashes and short synthetic content only.
- Bad recommendations/spam: enforce daily/session limits, cooldowns, min score, min coverage, repeated-dismiss suppression, and proposal expiry.
- Junk listing amplification: proposal acceptance/dismiss telemetry should affect listing evidence but not bypass S2 curation. Published listing status remains registry-controlled.
- Memory-api split-brain/outage: Scout treats catalog search as best-effort. No proposal is better than unscoped or stale proposals. Telemetry spools locally.
- Secrets: Scout reads credentials from existing
~/.m2-market/config.toml; never commits or logs keys. Log redaction is mandatory. - Host fragility: deploy canary-first, supervisor autorestart with backoff, health/status command, rollback by disabling supervisor and leaving state intact.
- Client tenant desktops: client identifiers can appear in herdr summaries. Summarizer must redact known tenant/client names where policy says shared telemetry; for tenant-private telemetry, still avoid raw text.
- Duplicate proposals: dedup by
{listing_id, intent_hash, session_id}plus cooldown. - False accept telemetry: Store/CLI install result events must include
proposal_idto count as accepted. Merely opening Store is not accepted.
7. [NEEDS CLARIFICATION]
- Exact M2 Store deep-link registration for Linux desktop: confirm whether
m2store://is final or whether S3 wants anm2-store --open-listingcommand-only contract. - Hermes chat bridge location: confirm whether Hermes should consume a local JSONL file, Unix socket, or m2-gpt subconscious event for
m2.hermes.market_proposal.v1. - Catalog tenant wildcard representation: confirm whether public listings use
tenant_visibility: ["*"]in registry payload and whether memory-api tenant filter should include"*"or a normalizedtenant:m2-sharednamespace.