# S6 — Proposal Engine & Evidence Loop SPEC ## 1. Scope & non-goals ### MVP scope S6 owns the pull-side proposal engine and pricing evidence loop: - Hermes `market-propose` skill: turns a user intent into build-vs-install-vs-job proposals. - Cost-intelligence layer: combines similar past work from memory, real token/time spend from m2-gpt metering/cognition traces, herdr run durations, and `market:catalog` search. - Evidence records: every serious job leaves a scrubbed, tenant-scoped record with wanted/tools/contributors/tokens/time/resources/failed/succeeded/reusable-verdict. - Proposal citations: proposals must cite listing evidence, similar job evidence, and cost estimates separately. - Harness-agnostic contract: Hermes is the primary surface; OpenAI-wire clients through m2-gpt can call the same tool/API later. MVP deploys as: 1. A Hermes skill installed by runtime-sync in desktop home volumes. 2. A small `m2-market-proposer` service on Coolify network for deterministic retrieval, estimate aggregation, evidence writes, and proposal JSON. 3. A gateway-side optional middleware hook that records real usage and exposes it by correlation id; the proposal decision logic stays outside the gateway. ### Later - Automatic quote negotiation with human operators. - Dynamic repricing of listings. - Public web marketplace proposal widgets. - Full resource-market routing optimizer. - Crypto-capable ledger integration beyond internal credits. ### Non-goals - Ledger schema or payment settlement: S1. - Catalog/listing truth, indexing, install flow, and M2 Store UX: S2/S3. - Scout push notifications: S4. - Packaging/build artifacts: S5. - Raw client transcript ingestion. S6 consumes scrubbed summaries only. ## 2. User stories + acceptance criteria ### US-1: Operator asks Hermes what path to take Given an operator asks Hermes for an outcome such as "automate my eBay listings" When Hermes invokes `market-propose` with the current intent and tenant identity Then the response contains at least one of each applicable path: - `install_adapt`: existing Solution covers enough of the intent. - `operator_job`: a Service/operator can deliver or adapt it. - `custom_build`: no adequate inventory exists or client constraints require custom work. Acceptance: - Every price/time/token estimate has at least one citation or is marked `low_confidence`. - Tenant visibility is enforced before any listing or evidence is returned. - Client-derived evidence is never cited unless `owner_initiated=true` and `double_scrubbed=true`. ### US-2: Proposal cites evidence without leaking data Given similar past work exists in memory under tenant-private and shared partitions When a proposal is generated for tenant `gst` Then only `tenant_id in ["gst", "__fleet__", "m2-core"]` and allowed shared records are searched. Acceptance: - Cross-tenant records are impossible to retrieve through the API filter. - Citations include stable IDs and short sanitized summaries, not raw transcript text. - The API returns `evidence_redactions[]` explaining omitted records by class, not identity. ### US-3: Serious job leaves an evidence record Given an install, operator-assisted job, or custom build runs longer than 10 minutes, spends more than 100k tokens, or creates a reusable artifact When the job ends or is abandoned Then `m2-market-proposer` writes a `m2.market.evidence.v1` record to memory and attaches its summary to any relevant listing. Acceptance: - Records validate against the schema below. - Failed jobs are recorded; they are usable for estimates but do not raise listing success stats. - Reusable verdict is one of `create_solution`, `update_solution`, `not_reusable`, `unclear`. ### US-4: Evidence improves future estimates Given at least three evidence records exist for a capability or listing When a later proposal asks for similar work Then estimates use p50/p80/p95 ranges instead of a single naive value. Acceptance: - Proposal JSON includes `estimate_basis.sample_count`. - Outliers remain in evidence but are downweighted only by explicit rule. - Install path estimates cite listing-level install telemetry before generic similar jobs. ## 3. Interfaces & data contracts ### Existing contracts S6 depends on - `memory-api`: live `https://memory.machinemachine.ai/health` returns ok; search endpoint shape is `POST /memory/search` with `query`, `agent_id`, `routing_strategy`, `limit`, `tenant_id`, and optional `filters`. - `m2-gpt`: live `https://gpt.machinemachine.ai/health` returns `{"status":"healthy","gateway":"bifrost"}`. It already has tenants/agents, `tenant_budgets`, `route_budgets`, `spend_log`, cognition traces, and usage passthrough from Bifrost. - `market:catalog`: existing catalog contract stores published listing payloads under memory partition `agent_id="market:catalog"`. - `m2.solution.v1` and `m2.listing.v1`: existing schemas already carry evidence, price, seller, tenant visibility, and stats. - `m2-ledger`: S6 quotes credits but does not debit; install/job settlement remains the ledger contract. ### Memory partitions Use memory-api `agent_id` as partition: - `market:catalog`: published listing semantic index, owned by S2/S3. - `market:evidence`: job and install evidence records, owned by S6. - `market:proposals`: optional proposal audit summaries, owned by S6. Tenant scope: - Shared evidence uses `tenant_id="m2-core"` or `tenant_id="__fleet__"`. - Tenant-private evidence uses canonical tenant id such as `gst`, `nasr`, `parlobyg`, `peter`. - Queries must pass `tenant_id=[caller_tenant, "m2-core", "__fleet__"]` unless caller is an operator-admin. ### Evidence record schema Schema id: `m2.market.evidence.v1`. ```json { "schema_version": "m2.market.evidence.v1", "evidence_id": "ev_2026w27_01j2abc", "job_id": "job_01j2abc", "tenant_id": "m2-core", "operator_id": "sdjs-operator", "agent_ids": ["chris-m2o"], "created_at": "2026-07-02T00:00:00Z", "wanted": { "intent": "Generate a branded competitor scan report", "normalized_capabilities": ["research", "report-generation"], "constraints": ["no raw client data in shared catalog"] }, "tools": [ {"name": "Hermes", "kind": "agent"}, {"name": "m2-gpt", "kind": "llm_gateway"}, {"name": "memory-api", "kind": "memory"}, {"name": "herdr", "kind": "run_capture"} ], "contributors": [ {"kind": "operator", "id": "sdjs-operator", "role": "builder"}, {"kind": "agent", "id": "chris-m2o", "role": "executor"} ], "metering": { "m2_gpt_correlation_ids": ["turn_01j2abc"], "prompt_tokens": 120000, "completion_tokens": 18000, "total_tokens": 138000, "cost_usd": 0.42, "wall_time_seconds": 2400, "active_time_seconds": 1500, "herdr_run_ids": ["run_2026-07-02T00-00-00Z"] }, "resources": [ {"kind": "desktop", "id": "chris-m2o", "seconds": 2400}, {"kind": "model", "id": "GLM-5.1", "tokens": 138000} ], "outcome": { "status": "succeeded", "failed": [ {"step": "site scraping", "reason": "blocked by login", "recovered": true} ], "succeeded": [ {"step": "report generation", "artifact_ref": "forgejo:m2/market-registry/..."} ] }, "reusable_verdict": { "decision": "create_solution", "confidence": 0.82, "reason": "Repeatable report workflow with scrubbed inputs" }, "listing_refs": [ {"listing_id": "lst_competitor-scan", "relationship": "supports_estimate"} ], "scrub_status": { "secrets_redacted": true, "pii_redacted": true, "double_scrubbed": true, "raw_sources_retained": false }, "content_hash": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" } ``` Required fields: `schema_version`, `evidence_id`, `job_id`, `tenant_id`, `created_at`, `wanted`, `tools`, `contributors`, `metering`, `resources`, `outcome`, `reusable_verdict`, `scrub_status`, `content_hash`. Memory store payload: ```json { "content": "Wanted: branded competitor scan report. Outcome: succeeded. Reusable: create_solution. Time: 40m. Tokens: 138k.", "agent_id": "market:evidence", "tenant_id": "m2-core", "metadata": { "schema_version": "m2.market.evidence.v1", "evidence_id": "ev_2026w27_01j2abc", "job_id": "job_01j2abc", "listing_ids": ["lst_competitor-scan"], "capabilities": ["research", "report-generation"], "status": "succeeded", "reusable_decision": "create_solution", "tokens_total": 138000, "wall_time_seconds": 2400, "content_hash": "sha256:..." } } ``` ### Proposal request schema Schema id: `m2.market.proposal-request.v1`. ```json { "schema_version": "m2.market.proposal-request.v1", "request_id": "prq_01j2abc", "tenant_id": "m2-core", "operator_id": "m2bd", "agent_id": "chris-m2o", "harness": "hermes", "intent": "I want to automate eBay listings", "context_summary": "User has product photos and wants listing drafts.", "constraints": { "max_credits": 150, "deadline": "2026-07-03", "data_sensitivity": "tenant_private", "allowed_inventory_types": ["solution", "service", "resource", "capability"] }, "current_tools": ["browser", "filesystem", "m2-market"], "correlation_id": "turn_01j2abc" } ``` ### Proposal response schema Schema id: `m2.market.proposal.v1`. ```json { "schema_version": "m2.market.proposal.v1", "proposal_id": "prop_01j2abc", "request_id": "prq_01j2abc", "tenant_id": "m2-core", "generated_at": "2026-07-02T00:00:00Z", "intent_summary": "Automate eBay listing creation from product inputs.", "paths": [ { "path_id": "path_install_adapt_1", "kind": "install_adapt", "title": "Install eBay Listing Workflow and adapt prompts", "recommendation": "recommended", "coverage": 0.72, "credits": {"min": 40, "expected": 60, "max": 90, "currency": "m2cr"}, "time": {"p50_seconds": 7200, "p80_seconds": 14400}, "tokens": {"p50": 600000, "p80": 1200000}, "resources": [{"kind": "desktop", "quantity": 1}], "next_action": { "type": "install", "listing_id": "lst_ebay-listing-workflow", "deeplink": "m2store://listing/lst_ebay-listing-workflow" }, "citations": [ {"kind": "listing", "id": "lst_ebay-listing-workflow", "claim": "catalog match"}, {"kind": "evidence", "id": "ev_2026w26_01x", "claim": "similar workflow completed in 2h"} ], "risks": ["Needs credentialed browser session", "May require image style adaptation"], "estimate_basis": {"sample_count": 4, "method": "listing_install_p80"} } ], "evidence_redactions": [ {"reason": "tenant_not_visible", "count": 3}, {"reason": "not_double_scrubbed", "count": 1} ], "audit": { "catalog_query_id": "memq_01j2abc", "evidence_query_id": "memq_01j2abd", "m2_gpt_correlation_id": "turn_01j2abc" } } ``` Path kinds: - `install_adapt`: buy/install Solution and adapt locally. - `operator_job`: engage operator/service through job-based pricing. - `custom_build`: build from scratch. - `resource_route`: rent internal resource/capacity when it is the dominant cost. - `defer`: no acceptable path under constraints. ### HTTP API: m2-market-proposer Service: `m2-market-proposer`, Coolify app on `coolify` network. Base internal URL: `http://m2-market-proposer:8000`. Auth: - `X-API-Key`: service key injected at runtime. - Optional `Authorization: Bearer ` for direct harness calls after gateway identity forwarding lands. #### `GET /health` Response: ```json {"status":"healthy","memory":"ok","m2_gpt":"ok"} ``` #### `POST /v1/proposals` Body: `m2.market.proposal-request.v1`. Response: `m2.market.proposal.v1`. Required behavior: 1. Normalize intent into capability tags. 2. Query `market:catalog` with tenant visibility. 3. Query `market:evidence` with tenant scope `[caller, "m2-core", "__fleet__"]`. 4. Fetch m2-gpt spend/time by `correlation_id` where available. 5. Build ranked paths with citations. 6. Store proposal audit summary in `market:proposals` unless `dry_run=true`. #### `POST /v1/evidence` Body: `m2.market.evidence.v1`. Response: ```json {"evidence_id":"ev_2026w27_01j2abc","stored":true,"attached_listing_ids":["lst_competitor-scan"]} ``` Behavior: - Validate schema. - Enforce scrub gate before shared write. - Store semantic summary in memory partition `market:evidence`. - Emit listing-attachment event for S2/S3 indexer. #### `GET /v1/evidence/{evidence_id}` Returns sanitized evidence record if caller tenant can see it; `404` for not found or forbidden to avoid existence leaks. #### `POST /v1/estimates` Body: ```json { "tenant_id": "m2-core", "intent": "build competitor scan report", "listing_ids": ["lst_competitor-scan"], "capabilities": ["research", "report-generation"], "confidence_floor": 0.6 } ``` Response: ```json { "sample_count": 5, "tokens": {"p50": 300000, "p80": 800000, "p95": 2000000}, "time_seconds": {"p50": 1800, "p80": 7200, "p95": 172800}, "credits": {"p50": 30, "p80": 90, "p95": 300}, "method": "similar_evidence_weighted", "citations": [{"kind":"evidence","id":"ev_..."}] } ``` ### CLI verbs Add to existing `m2-market` CLI surface: ```bash m2-market propose "" --tenant --operator --json m2-market evidence capture --job-id --from-herdr --correlation-id m2-market evidence submit evidence.json m2-market evidence show --json ``` Exit codes follow existing CLI contract: `0 ok`, `1 usage`, `2 not found`, `5 validation/firewall rejection`. ### Hermes skill: `market-propose` Install path: - Primus: `/home/developer/.hermes/skills/market-propose/` - Agent-latest/OpenClaw-compatible fallback assets: `/home/developer/.m2-core/skills/market-propose/` - Distributed by runtime-sync/m2-core-sync only; no baked secrets. Skill files: ```text market-propose/ SKILL.md skill.json scripts/propose.py scripts/evidence_capture.py ``` `skill.json`: ```json { "name": "market-propose", "version": "0.1.0", "runtime": "python3", "commands": { "propose": "python3 scripts/propose.py", "evidence_capture": "python3 scripts/evidence_capture.py" }, "env": [ "M2_MARKET_PROPOSER_URL", "M2_MARKET_API_KEY", "M2_GPT_CORRELATION_ID", "M2_TENANT_ID", "M2_OPERATOR_ID" ] } ``` Hermes behavior: - Invoke `propose` when the user asks for cost, build/install choice, "is there a package", "can someone do this", or "estimate this job". - Render concise human prose, but preserve machine-readable proposal JSON in an attached block or local state file. - Do not install or spend credits without explicit user approval. ### m2-gpt metering contract S6 consumes existing gateway data instead of duplicating metering: - `spend_log`: tenant, route/model, recorded time, prompt/completion/total tokens where present, cost. - cognition trace/audit v2: `tenant_id`, `agent_id`, `turn_id`/correlation id, `model`, `tokens`, `tool`, `status`, latency/time. - Bifrost OpenAI response `usage`: passthrough source for token counts. Add read-only internal endpoint in m2-gpt: #### `GET /internal/v1/metering/turn/{correlation_id}` Auth: service key, internal network only. Response: ```json { "correlation_id": "turn_01j2abc", "tenant_id": "m2-core", "agent_id": "chris-m2o", "started_at": "2026-07-02T00:00:00Z", "ended_at": "2026-07-02T00:12:00Z", "model_calls": [ { "route_id": "spark-glm", "model": "GLM-5.1", "prompt_tokens": 120000, "completion_tokens": 18000, "total_tokens": 138000, "cost_usd": 0.42, "status": "ok" } ], "totals": { "prompt_tokens": 120000, "completion_tokens": 18000, "total_tokens": 138000, "cost_usd": 0.42, "wall_time_seconds": 720 } } ``` This endpoint is a gateway concern but S6 defines it as the minimum metering read needed for evidence capture. If not available in MVP, `evidence_capture` records `m2_gpt_correlation_ids` and `tokens_unknown=true`. ### Events S6 emits JSONL-compatible events to stdout and optional webhook for S2/S3: ```json { "event_type": "market.evidence.created", "event_version": "v1", "ts": "2026-07-02T00:00:00Z", "evidence_id": "ev_2026w27_01j2abc", "tenant_id": "m2-core", "listing_ids": ["lst_competitor-scan"], "reusable_decision": "create_solution" } ``` Other events: - `market.proposal.generated` - `market.proposal.accepted` - `market.proposal.dismissed` - `market.evidence.attached_to_listing` ### Storage and attachment Authoritative evidence storage: - Memory semantic copy: `agent_id="market:evidence"` in memory-api. - Audit copy: JSONL at proposer service volume `/var/lib/m2-market-proposer/evidence/YYYY-Www.jsonl`. - Listing attachment: S6 does not mutate listing truth directly. It emits `market.evidence.attached_to_listing`; S2/S3 indexer updates `listing.evidence_summary`, listing stats, and optional registry audit PR/commit. Proposal audits: - Stored as summaries under `agent_id="market:proposals"` with `tenant_id` scoped to caller. - TTL/default retention: 90 days for tenant-private, indefinite for shared scrubbed proposal statistics. ## 4. Integration contract with other subsystems ### S1 ledger Inputs from S1: - Current price for install/job/resource. - Job escrow/quote status when path kind is `operator_job`. - Platform cut defaults only for display; S6 does not settle. Outputs to S1: - Approved path metadata may become ledger `reason=route|install|job` ref. - Proposal id should be included in ledger refs where possible: `prop_...:path_...`. ### S2 catalog Inputs from S2: - `market:catalog` searchable listing payloads. - Listing stats: installs, rating, proposals_shown, proposals_accepted. - Tenant visibility policy. Outputs to S2: - `market.proposal.generated/accepted/dismissed` events. - Evidence attachment events by listing id. - Suggested new capability tags from proposal normalization. ### S3 store/install Inputs from S3: - M2 Store deep links: `m2store://listing/`. - Install state and permission diff when available. Outputs to S3: - Proposal path next actions with listing ids. - Evidence of install success/failure for listing stats. ### S4 Scout Shared contracts: - S4 can call `POST /v1/proposals` with `harness="scout"` and a session summary. - S4 owns push timing/UI; S6 owns proposal construction and estimate basis. - Proposal/dismissal telemetry uses the same events. ### S5 packaging Inputs from S5: - Build/package evidence created during solution packaging. - Manifest ids, release refs, tool requirements. Outputs to S5: - Reusable verdicts: `create_solution` and `update_solution`. - Evidence citations that should be included in a future `solution.evidence[]`. ### m2-gpt Touchpoints: - Tenant/agent identity from bearer key. - m2-gpt `spend_log` and cognition traces by `correlation_id`. - Optional gateway middleware/tool-dispatch registration so all OpenAI-wire harnesses can call proposal tool. No S6 secrets are baked into images. `M2_MARKET_API_KEY` and `M2_GPT_API_KEY` are runtime-injected. ### memory-api Touchpoints: - `POST /memory/search` with `agent_id="market:catalog"` and `agent_id="market:evidence"`. - Store via existing memory write path once auth is enforced. - Tenant/principal filters must be passed on every query. ### m2o/herdr Touchpoints: - `herdr` run summaries supply active time, failed/succeeded steps, tools invoked. - Desktop identity from fleet/herdr config supplies `agent_id`. - Runtime-sync installs the Hermes skill across primus and agent-latest without image rebuild. ## 5. Options compared ### Option A: Hermes skill only Pros: - Fastest MVP. - Fits primary agent surface. - Can render proposals conversationally. Cons: - Duplicates retrieval/estimate logic if OpenClaw or Store needs it. - Harder to centralize evidence writes and audit. ### Option B: m2-gpt middleware/tool only Pros: - Harness-agnostic by default for anything OpenAI-wire. - Already sees identity, tokens, model usage, and budgets. Cons: - Bloats latency-sensitive gateway. - Proposal logic depends on catalog and ledger services, which should not be in the critical chat-completion path. - Harder to evolve independently. ### Option C: Proposer service + Hermes skill adapter (recommended) Pros: - Keeps deterministic business logic and evidence writes centralized. - Hermes gets a thin local skill; OpenClaw/Store/Scout can call the same API. - m2-gpt only provides identity/metering and optional tool exposure. - Can be canary-deployed as a Coolify app and rolled back independently. Cons: - One new small service to operate. - Requires service keys and network ACLs. Recommendation: Option C for MVP. Add gateway middleware only for two narrow functions: expose `market.propose` as an OpenAI tool later, and provide read-only metering by correlation id. ### Cost estimate method Compared: - Static price table: simple but ignores real work history. - LLM-estimated effort: flexible but unverifiable. - Evidence-weighted estimates: uses real prior tokens/time/outcomes and listing telemetry. Recommendation: evidence-weighted estimates. Fall back to static seed values only when `sample_count < 2`, and mark as `low_confidence`. ## 6. Risks/edge cases - Tenant leakage: all memory searches must include tenant filters; forbidden evidence returns `404`, not `403`. - Client-derived commercialization: shared evidence requires `owner_initiated=true` and `double_scrubbed=true`; otherwise tenant-private only. - Secrets in evidence: evidence capture uses the fedlearn scrubber policy and fails closed on high-entropy strings. - Bad estimates from tiny samples: responses must expose `sample_count`, `method`, and confidence. - Failed jobs hidden from pricing: failed evidence is required because it improves estimates; proposals must distinguish success evidence from failure evidence. - Gateway fragility: do not put catalog search inside the hot chat-completion path; proposer service is called explicitly by skill/tool. - Abuse/spam proposals: proposal audits are rate-limited per operator and do not auto-spend credits. - Split-brain memory-api: indexer/proposer must use the live endpoint resolver until fedlearn T13 is complete; evidence JSONL audit copy allows replay. - Clock/correlation mismatch: if m2-gpt turn id is missing, evidence can still record herdr run ids and `tokens_unknown=true`; later backfill may attach metering. - Cross-harness UX drift: Hermes prose is non-authoritative; proposal JSON is the contract. ## 7. [NEEDS CLARIFICATION] 1. What exact m2-gpt correlation id is guaranteed to be available inside Hermes skill execution: `turn_id`, `correlation_id`, or a new `M2_GPT_CORRELATION_ID` env var? 2. Who is allowed to mark tenant-private evidence as `owner_initiated=true` and `double_scrubbed=true`: tenant admin, platform operator, or both? 3. What is the first static fallback conversion from USD/token cost to M2 credits for estimates before ledger/resource pricing data is available?