LINKEDIN: LinkedIn Engagement - Comments on the right people's posts

The actual documents the agents read and work from, shown exactly as they are on disk — not a summary. See the progress view instead · All projects

Plan PLAN.proposed.txt

# PLAN — GROUP D, LINKEDIN ENGAGEMENT — comments on the right people's posts, then outreach (2026-09-09 shape)

Owner: the Group D overseer. Written 2026-09-10 from the finalized LinkedIn Engagement and Outreach spec, on Nick's instruction the same day: *"were going to have a fable agent create a plan as i wnat to build this into the hub"*. This is a NEW lane beside `projects/ops/life-os/REGROUP-2026-09-08/plans/JASMIN-CAPTUS/PLAN.proposed.txt`, which keeps its own scope untouched — that lane writes and approves posts FOR a client, this one comments and reaches out AS Nick. Neither file governs the other and neither is replaced.

Plain name: LinkedIn — comments on the right people's posts

**🔴🔴 THIS IS THE ONLY PLANNING DOCUMENT FOR THIS LANE. Do not create a second plan, tracker, summary, or scratch state file — extend THIS file or its PROGRESS.txt companion. Any status view is GENERATED from this plan; if a view disagrees with the plan, the plan wins.**

**NORTH STAR:** Nick opens the Hub and finds comments already written on today's posts by the exact people he wants in front of — each one naming something specific in that post and actually saying something, none of them "great post" — he approves, edits or skips each with one click, and the ones he approved appear under those posts on his own LinkedIn account at a sensible moment, at a human pace, with the replies they earned counted back.

**FINISH LINE:** each item passes its one check, driven by an agent, never by Nick — (a) a written description of who he wants to reach turns into a real named list of people, and a hand-picked list can be dropped in instead; (b) a reshare, a one-liner and an engagement-bait post are all thrown away and a post with something in it is kept; (c) every comment candidate names a specific number, claim or example from the post it answers and takes a position, and a praise-only candidate is refused by the machine, not by taste; (d) a candidate carrying known AI tells never reaches Nick's screen, and the checker is shown refusing one before it is trusted; (e) nothing whatsoever reaches LinkedIn without Nick's own click on the card, and a robot attempting to approve is refused; (f) an approved comment appears under the real post between 20 and 90 minutes after that post went live, at a time no two days share, and the day's count never passes the ceiling; (g) the replies, reactions and profile views a comment earned are read back and the next drafts lean toward what earned them; (h) one Hub screen shows the target list, today's pace and what each comment did, measured at zero against its locked drawing; (i) a connection note or a direct message goes out only after Nick's click on that specific message, and a direct message before a connection is accepted is impossible. Written once, never raised mid-drive.

**Owner:** the Group D overseer · **Overseer:** ONE — Fable or Opus for this lane; never builds · **Design authority:** Sienna, UI only, once a fidelity count reaches zero
**Rule: a step starts the moment its named inputs exist, whatever its number. A step closes on ONE independent check by a different model. Nothing waits on Nick to test.**

### STEP 0 — ARM THE LOOP, BEFORE ANYTHING ELSE
Set a 5-minute loop. Every time it fires, answer these four in order and CORRECT any failure before doing anything else:
1. **NORTH STAR** — is what I am doing this minute moving this plan's North Star? If not, drop it and take the highest-value unblocked step that does.
2. **FAN-OUT** — is every step whose START WHEN inputs exist running, up to the cap of 8? Below the cap with ready work: dispatch now. At the cap: queue, never launch. One at a time for anything that launches a browser, through the machine-wide lock.
3. **CHEAP** — is every build and every check on a cheap model by name? A refusal from the router is a failure to log (Nick, 2026-09-09), never a reason to promote the job to Sonnet or Fable; a cheap vendor failure goes to the named backup.
4. **BLOCKED** — is anything "waiting"? Re-read its START WHEN line; if the artefact exists, start it; if it truly does not, one line to the overseer naming the ONE missing thing, and on to the next step.

## Already true (facts, not story)

- MEASURED 2026-09-10, and it is the reason the sourcing step is cheap rather than new: DeepAPI's LinkedIn people search is live on this account and answers a zero-spend preview. A request carrying titles, locations and `maxItems: 50` with `dryRun: true` returned `status: dry_run` and `debitMicrousd: 0` — evidence: `POST $DEEPAPI_API_BASE_URL/v1/scrape/linkedin/people`, run 2026-09-10, exit 0, and the live contract read the same minute from `GET $DEEPAPI_API_BASE_URL/v1/capabilities?capability=scrape.linkedin.people`, which names the body fields `query`, `titles`, `locations`, `companies`, `schools`, `includeDetails`, `maxItems`, `maxCostUsd`, `dryRun` and states the floor: `maxCostUsd defaults to 1.25 (2.50 with includeDetails) and cannot go lower`
- BUILT, and it is the target-watching mechanism this lane reuses rather than invents: a working call against DeepAPI's LinkedIn posts endpoint that reads a PROFILE's recent posts, with its request shape measured on this machine on 2026-09-09 — evidence: `projects/ops/skippy-jobs/jobs/captus-tracker-refresh.mjs`, whose header records `POST /v1/scrape/linkedin/posts, body {profiles:[<profile url>], maxItems, since}, header Idempotency-Key required; measured 2026-09-09`, and whose `deepapiPosts()` is the live caller
- BUILT: the Hub's approval queue already refuses a robot. A decision on a proposal is signed-session-only and returns 401 to a bearer token, in the door's own words, so "nothing reaches a person without Nick's click" is an existing property of the code rather than something this lane must invent — evidence: `projects/business/business-app/app/functions/api/proposals.js`, its `decide()` path
- BUILT: a proposal KIND is a three-line change to that door plus one import-only effect module, already done once for Captus drafts — evidence: `projects/business/business-app/app/functions/api/_captus-draft.js` and the three touch points in `projects/business/business-app/app/functions/api/proposals.js` (the import, the daily-cap exemption, the post-commit effect hook)
- BUILT: the approve / edit-and-approve / decline card that Nick already reads in the Inbox is a reusable component, so "one click each: approve, edit, skip" needs no new control — evidence: `projects/business/business-app/app/js/proposals.js`, its `BZ.Proposals.queueCard`
- BUILT: a Hub screen is a five-file mechanical change with no framework and no client build step — evidence: `projects/business/business-app/app/js/app.js` (the `VIEWS`, `TITLES` and `TAB_VISIBILITY` lists), `projects/business/business-app/app/index.html` (the `#view-artifacts` section and the nav tab beside it), and `projects/business/business-app/app/js/artifacts.js`, whose closing `BZ.register` line is the canonical minimal example
- BUILT: the working pattern for a browser that holds a real signed-in session on a site we do not own — a dedicated persistent profile, paired once by Nick himself, with no code path that ever types a credential and a hard guard against the URLs that must never be touched — evidence: `projects/personal/skippy-app/cart/browser.mjs` and `projects/personal/skippy-app/cart/pair.mjs`
- BUILT: one shared headless browser rig with no npm dependencies, and a machine-wide lock so two lanes never launch Chrome at once — evidence: `projects/shared-tooling/browser.mjs` and `projects/ops/skippy-jobs/lib/with-chrome-lock.mjs`
- BUILT: the anti-slop instrument that actually runs today is the AI-writing-tell checklist, invoked by name inside the verify recipe — evidence: `ZION/skills/nicksvoice/SKILL.md` and `ZION/skills/jasmin-verify/SKILL.md`, whose step 8 calls it
- MEASURED 2026-09-10, and this is why the plan does not rest on it: the humanizer service named in the Captus tracker has never run. Its key authenticates but is refused for lack of the humanizer scope, and the one real nine-post comparison against it recommends against treating a pass as an improvement — evidence: `projects/business/marketing-sales/clients/captus/PROJECT.md` (the recorded refusal `API key does not have required scope: Humanizer`) and `projects/business/marketing-sales/clients/captus/WALTER-COMPARISON-2026-09-03.md` (its own words: `don't treat "ran through Walter" as "improved."`)
- MEASURED 2026-09-10, and it is the one genuinely new component: nothing anywhere holds a signed-in LinkedIn session, and DeepAPI's browser endpoint cannot create one. The encrypted vault holds 131 entries and none of them is a LinkedIn login — evidence: `python3 projects/personal/family-vault/vault.py list --caller=skippy`, run 2026-09-10, exit 0, 131 rows, zero matching `linkedin`; and `/Users/nickdeck/.agents/skills/deepapi/references/browse-web.md`, whose safety block reads `Public web only: tasks that need logins, credentials, account creation, CAPTCHA solving, or purchases are rejected.`
- MEASURED 2026-09-10: neither existing Captus job is on the clock — `command grep -c captus projects/ops/skippy-jobs/runner.mjs` returns 0 — so this lane's own cadence is a handoff to the SCHEDULED lane and never an assumption
- DECIDED, not built: engaging in other people's comments is already the doctrine this lane's whole thesis rests on, written down before this plan existed — evidence: `projects/business/marketing-sales/agents/jasmin/doctrine.md`, its line `Comments over posting. Engaging genuinely in comments — his own and others' — is treated as equal or greater leverage than the post itself.`
- Nick's rulings on file, never asked again: agents drive the real click paths and he is never the tester (2026-09-09); exactly four things need his yes, and a message sent as him to another human is the fourth (MACHINE-RULES Rule 13); nobody chases security or privacy (2026-09-09); the cheap lane builds and checks and a wall refusing it is a failure to log, never a promotion to Sonnet (2026-09-09); a cold-collected person is never added to any bulk email audience, permanently and with no override — evidence: `projects/ops/agents/AGENT-SYSTEM-SPEC.md`, decision row D-e

## 0 · Gate Zero receipts (the plan may not exist without these)
- Failure Mode Registry loaded: 2026-09-10, 193 entries, as counted by the plan checker itself when it read `.claude/skills/plan/references/failure-registry.md` during this plan's own check run; the ten failures this lane is genuinely exposed to are named in §4, each with the measure that prevents it
- Canonical specs loaded: the plan skill `ZION/skills/plan/SKILL.md` (2026-09-09 shape) and its `references/plan-template.md`; `projects/ops/agents/DESIGN-FIDELITY-STANDARD.md`; `projects/ops/agents/CODE-STANDARD.md`; the agent system spec `projects/ops/agents/AGENT-SYSTEM-SPEC.md`, which owns collecting people and bars them from bulk email; the content doctrine `projects/business/marketing-sales/agents/jasmin/doctrine.md`
- Ownership check: no plan, agent or job owns LinkedIn engagement today. `projects/ops/artifacts/project-status/registry.json` holds 38 rows and none of them names LinkedIn engagement or outreach; the only prior writing on the subject is a ten-line sketch, `projects/ops/agents/expertise/playbooks/director-of-marketing/linkedin-engagement-lead-pipeline.md`, which is a playbook and not a build, and which this plan supersedes in practice by naming DeepAPI where it named a third-party scraping vendor. The collecting engine that DOES exist has a different subject and a different owner: `projects/business/marketing-sales/LEAD-ENGINE-SPEC.md` is marked superseded in its own first ten lines by `projects/ops/agents/AGENT-SYSTEM-SPEC.md`, whose Lead Generator collects COMPANIES for the new business from public sources — this lane collects PEOPLE for Nick's own LinkedIn account, writes nothing to the prospect store and nothing to the contact list, and §3 freezes that boundary
- Expected inputs confirmed to exist: the DeepAPI credentials file `/Users/nickdeck/.deepapi/env` (present, both variables set, mode 600) and its people endpoint (dry-run RUN 2026-09-10, `debitMicrousd: 0`); the posts endpoint's measured caller `projects/ops/skippy-jobs/jobs/captus-tracker-refresh.mjs` (opened); the Hub approval door `projects/business/business-app/app/functions/api/proposals.js` and the kind precedent `projects/business/business-app/app/functions/api/_captus-draft.js` (both opened); the queue card `projects/business/business-app/app/js/proposals.js` (opened); the Hub route table `projects/business/business-app/app/js/app.js` and shell `projects/business/business-app/app/index.html` (opened); the key-value write helper `projects/business/business-app/app/functions/api/_kv.js` and the identity resolver `projects/business/business-app/app/functions/api/_session.js` (opened); the paired-session precedent `projects/personal/skippy-app/cart/browser.mjs` and `projects/personal/skippy-app/cart/pair.mjs` (opened); the shared browser rig `projects/shared-tooling/browser.mjs` and the lock `projects/ops/skippy-jobs/lib/with-chrome-lock.mjs` (opened); the anti-slop checklist `ZION/skills/nicksvoice/SKILL.md` (opened); the cheap routers `projects/ops/cheap-task.mjs` and `projects/ops/route-build.mjs` (opened); the vault `projects/personal/family-vault/vault.py` (RUN, exit 0, 131 entries)
- PLAN AUTHOR: the Opus session of 2026-09-10 that took over this writing task in-thread when the Fable session it was dispatched to hit its usage limit, under Nick's standing rule for that exact situation
- COLD READER: none — SINGLE-AUTHOR, UNREVIEWED — the Group D overseer's pickup read is this plan's one cold read
- PROMPT-SPEC scan (P1–P7): P3 fired on "the same humanizer pass Jasmin content already goes through" — re-measured rather than assumed, and the answer is that the named humanizer service has never run and its one evaluation is negative, so the gate in this plan is the checklist that does run, with the service named in §7 as an upgrade and not a dependency; P3 fired again on "a real, logged-in LinkedIn browser session (cloud-hosted headless browser)" — the live contract for the hosted browser endpoint was read and it refuses logins outright, so the session-holder is built on the paired-profile pattern that already works here, with the hosted-and-proxied version named as §7 item 1; P1 fired on "hypertargeted search, not a broad list" — read as a written, named, saved description of who he is after, which a run refuses to start without, mirroring the target-definition rule the agent system spec already enforces; P7 fired on "approval ... ON-DEMAND for v1 ... designed so it can switch to a daily-batch review screen later" — read as the existing approval queue in the Inbox for v1, with the batch screen being STEP 8's own screen, so the switch is a change of surface and never a change of mechanism; P4 fired on "scrape contacts", which could mean a login-holding scraper — read as the sanctioned people-search endpoint only, per the standing pattern in the agent system spec

## 1 · Goal and definition of done
- **What we're building, one paragraph.** A second lane inside the content pipeline that already writes posts: this one writes comments on other people's posts, and later connection notes and direct messages, in Nick's own voice, on Nick's own account. It finds the people from a written description of who he wants to reach, watches what they publish, throws away the posts with nothing in them, drafts two or three comments that each name something specific in the post and take a position, refuses any that read like a machine wrote them, puts each in front of Nick as one card he approves, edits or skips with a single click, posts the approved ones through a browser he signed into himself between twenty and ninety minutes after the post went live at a pace no faster than a person, and counts what came back so the next drafts lean toward what earns replies.
- **HOW IT'S USED:** Nick opens the Hub during the day, sees a handful of comment cards among the things already waiting for him, reads the post it answers and the comment drafted for it, and clicks approve, edit or skip; later he opens one screen to see who is on his target list, how many comments went out today, and which ones got replies. · HOW WE KNOW: his instruction of 2026-09-10 to build it into the Hub, and the approval card he already uses in the Inbox for every other proposal.
- **WHAT IT LOOKS LIKE:** for v1, the Hub card he already knows — one line saying what will happen, the post above and the drafted comment below, with approve, edit and skip; plus one new Hub screen, LinkedIn, holding the target list, today's pace and each comment's result. Nothing about it is restyled. · HOW WE KNOW: the queue card component in `projects/business/business-app/app/js/proposals.js` and the screen pattern in `projects/business/business-app/app/js/artifacts.js`.
- **WHERE IT LIVES:** the Hub at hub.heroesandsidekicks.io, opened by Nick and by nobody else in v1; the drafting, watching and posting jobs on the Mac in `projects/ops/skippy-jobs/jobs/`; the target list, the candidates and the results in the Hub's own key-value store under a new `bizapp:` key written only through `projects/business/business-app/app/functions/api/_kv.js`. · HOW WE KNOW: the store map in that helper and the job pattern in `projects/ops/skippy-jobs/jobs/captus-tracker-refresh.mjs`.
- **WHAT IT MUST DO:** (1) turn a written description of the people Nick wants to reach into a real named list, and accept a hand-picked list instead; (2) watch every person on that list for new posts and discard the ones with nothing to answer; (3) draft two or three comments per kept post, each naming a specific number, claim or example from that post and taking a position; (4) refuse any candidate carrying known machine-writing tells before Nick ever sees it; (5) put every candidate in front of Nick as one card with approve, edit and skip, and make it impossible for anything to reach LinkedIn without his click; (6) post an approved comment on the real post between twenty and ninety minutes after it went live, at a randomised time, inside a hard daily ceiling; (7) read back the replies, reactions and profile views each comment earned and bias the next drafts toward what earned them; (8) show the target list, the day's pace and each comment's result on one Hub screen measured at zero against its locked drawing; (9) send a connection note, and later a direct message, through the same draft-refuse-approve-send loop, with a direct message impossible before the connection is accepted.
- **NOT in scope:** the ANTI-SCOPE — (a) anybody's LinkedIn account but Nick's own: no client account, no company page, no second identity, deliberately, because the pacing ceiling and the session are per-account and one account is the honest unit to prove it on; (b) security, privacy and credential work of any kind, including anything about how the browser profile is stored: one line in `projects/ops/sp-sec/PLAN.md` and back to building; (c) writing anything into the prospect store, the client roster or the contact list — the Lead Generator in `projects/ops/agents/AGENT-SYSTEM-SPEC.md` owns those and this lane has no path to them, by design and not by discipline; (d) the Captus client work — posts, the client approval screen, the client's voice file and the tracker all belong to `projects/ops/life-os/REGROUP-2026-09-08/plans/JASMIN-CAPTUS/PLAN.proposed.txt` and are untouched here; (e) putting this lane's jobs on a clock — the SCHEDULED lane owns every clock and receives a handoff line; (f) any login-holding scraper of LinkedIn built from scratch for READING — the sanctioned people and posts endpoints are the only read path, because the account's safety is worth more than the extra fields; (g) bulk sending of any kind, at any volume, ever — every single outbound item is one card and one click; (h) a second approval system — the queue that already refuses robots is extended with one kind, never duplicated.
- **Trip-over protocol:** a lane that finds something outside the fence writes one handover line to its named owner (a security- or privacy-shaped thing: one line in `projects/ops/sp-sec/PLAN.md`), then back to building — never investigates, never fixes.

## 1a · Critical variables — the confirmation sheet is GENERATED from this table

| # | The variable, in plain words | Value chosen | Alternatives rejected | Class | HOW WE KNOW | Cost if wrong | CONFIRMED |
|---|---|---|---|---|---|---|---|
| 1 | **SURFACE — which screen this lands on, and who opens it** | the Hub at hub.heroesandsidekicks.io, opened by Nick: the approval card he already reads in the Inbox for v1, and one new LinkedIn screen for the target list and the results | a separate site; a daily email digest; a screen inside the client-facing Captus page | V1 | his own instruction naming the Hub as the home for this | he never sees the drafts, and the whole lane produces nothing he acts on | Nick, 2026-09-10, "were going to have a fable agent create a plan as i wnat to build this into the hub" |
| 2 | Whose account v1 runs on | Nick's own LinkedIn account only, and no other, with the code written so a second account is possible later and none is built now | a client's account; several accounts at once; a company page | V1 | the finalized spec's explicit scope decision, relayed the same day | a client's account carries the risk of an unproven pacing ceiling, which is the one thing that cannot be undone | Nick, 2026-09-10, via the finalized spec: "v1 runs on Nick's own LinkedIn account only (not a client's — that's an explicit, deliberate scope decision for this phase)" |
| 3 | What a click means | one click on the card is the approval for that one comment or message, and nothing reaches LinkedIn without it; a robot decision is refused by the door itself | a batch approval of a day's drafts; a standing approval for a target; an approved-by-default queue with an undo | V1 | the fourth approval class, and the door's existing robot refusal | a message goes out as Nick that he never agreed to, which is the one failure with no undo | Nick, 2026-09-10, via the finalized spec: "One click each: approve / edit / skip. Nothing posts without a click" |
| 4 | Comments first, outreach after | the comment pipeline is built, proved and judged before any connection note or direct message is drafted; the outreach stage is written on the same spine and switched on by his word | building both together; starting with connection requests because they are simpler | V1 | his stated order, and the fact that the comment lane is the one that can be proved on his own post without a message to anyone | the first thing he judges is the riskiest half, and a bad first impression lands on real people | Nick, 2026-09-10, via the finalized spec: "he wants to see and judge this piece before greenlighting the rest" |
| 5 | The pace | roughly 15 to 25 connection requests a day and the same order of magnitude of comments, randomised, never a burst, treated in code as a ceiling that refuses rather than a setting that can be raised | a tunable number in a settings file; no ceiling until something goes wrong | V1 | the spec's own words that this is a constraint and not a preference | the account is restricted, and every other part of this build becomes worthless at once | Nick, 2026-09-10, via the finalized spec: "This is a hard technical ceiling on account safety, not a tunable 'go faster' setting" |

- V1 confirmation reads `<name>, <date>, "<their own words>"` — the date is required.
- V2 confirmation reads `opened <what>, <date>, saw: <what was actually there>`.

**Considered and ruled NOT critical:**
- `which cheap vendor builds which step` — the model matrix decides it; a wrong pick costs one failover, not a different product.
- `where the target list is stored` — the Hub's own key-value store, by the ownership rule; nothing else in this build reads it.
- `whether the substance filter is a model call or a rule` — it is proven by fixtures either way, and the fixtures are the contract.

## 1b · Subproject decomposition — could a piece of this ship on its own?

| Subproject | End goal (one sentence — what's TRUE when done) | Depends on (named artefact) | Owner | Own PLAN.md path | Confirmation-sheet status |
|---|---|---|---|---|---|
| Finding and watching | a written description of who Nick wants becomes a named list, and every post those people publish is fetched and either kept or discarded | none — start now | this lane | this file, STEP 1 and STEP 2 | §1a signed |
| Writing and refusing | two or three comment candidates per kept post, each specific and taking a position, with the machine-sounding ones refused before Nick sees them | STEP 2's kept posts | this lane | this file, STEP 3 and STEP 4 | §1a signed |
| Approving and posting | Nick's click is the only path to LinkedIn, and an approved comment lands under the real post at a human moment inside a hard ceiling | STEP 4's surviving candidates | this lane | this file, STEP 5 and STEP 6 | §1a signed |
| Seeing what it did | the replies each comment earned are read back, the next drafts lean toward them, and one Hub screen shows the list, the pace and the results | STEP 6 closed | this lane | this file, STEP 7 and STEP 8 | §1a signed |
| Outreach | a connection note and then a direct message travel the same draft-refuse-approve-send loop, one click each | §7 item 3, and STEP 6 closed | this lane | this file, STEP 9 | §1a signed |
| Polish | every screen state opened once in every size and theme, and the lane closed with its postmortem written | the FRONT steps closed | this lane | this file, STEP 10 and STEP 11 | §1a signed |

**Carve-out rule:** putting this lane's jobs on a clock is carved out to the SCHEDULED lane by the handoff line on STEP 6; the hosted-and-proxied browser is carved out to §7 item 1 with its default named; nothing else is left out.

## 2 · The complete UX map (this becomes the test manifest verbatim)

| Id | Screen / entry point | State (default·empty·error·loading) | Element / interaction | Expected behavior | Navigation from → to |
|---|---|---|---|---|---|
| L1 | Hub → LinkedIn screen → the target list | populated · empty · loading · error | the ICP description and its Save control | a saved, named description of who Nick wants to reach; saving it is what lets a run start, and a run with none refuses in words rather than guessing | LinkedIn → LinkedIn |
| L2 | Hub → LinkedIn screen → the target list | populated · empty | Import a list | a hand-picked list of profile addresses is accepted and merged into the same list, marked as hand-picked | LinkedIn → LinkedIn |
| L3 | Hub → Inbox → a comment card | default · approved · edited · skipped · refused | Approve, Edit and approve, Skip | one line first saying exactly what will happen, the post it answers above and the drafted comment below; one click decides it; the card leaves and stays gone on reload | Inbox → Inbox |
| L4 | Hub → Inbox → a comment card | robot attempt | a decision arriving with a robot token instead of Nick's session | refused with 401 and nothing written; the card is still waiting afterwards | Inbox → Inbox |
| L5 | Hub → Inbox → a comment card | edited | Edit and approve | the words Nick typed are the words that post, character for character, and the original is kept beside them | Inbox → LinkedIn |
| L6 | LinkedIn itself, an approved comment | posted · refused · retried | none — the posting job | the comment appears under the real post between 20 and 90 minutes after it went live, at a time drawn fresh each day; LinkedIn's own copy reads back identical | Inbox → LinkedIn |
| L7 | LinkedIn itself, the day's ceiling | at the ceiling | the posting job attempting one more | it refuses and says so in the day's line; nothing is posted; the item waits for tomorrow | job → job |
| L8 | Hub → LinkedIn screen → results | populated · empty · loading | each comment's row | the replies, reactions and profile views that comment earned, with the date they were read and where they came from | LinkedIn → LinkedIn |
| L9 | Hub → LinkedIn screen | default | the whole screen, 1440 and 390, light and dark | matches its locked drawing at zero mismatched properties and zero unmeasured anchors | any → LinkedIn |
| L10 | Hub → Inbox → a connection note card | default · approved · skipped | Approve, Edit and approve, Skip | the note names the same specific thing about that person the comment engine found; one click sends exactly one note | Inbox → LinkedIn |
| L11 | Hub → Inbox → a direct message card | connection not yet accepted | none — the drafting job | no direct message card can exist for a person whose connection has not been accepted; the attempt is refused and named | job → job |
| L12 | Every state above | loading · empty · populated · error | opened as Nick, 1440 and 390, light and dark | nothing else in the Hub changes; no other identity sees this screen or these cards | any → any |

## 2d · DESIGN FIDELITY GATE (plan skill §D — mandatory when the deliverable is looked at)

- **LOCKED TARGET:** `NOT YET LOCKED — STEP 8 builds it` — a generated LinkedIn-screen drawing published login-free beside its generator, in the Hub's own tokens, approved by Nick before the first measurement. The comment card itself is NOT a new target: it is the Hub's existing queue card, whose look the HUB lane owns and has already locked, and this lane changes none of it.
- **TARGET HASH:** `shasum -a 256` of the generator's output, machine-written into the lane's evidence folder by STEP 8 · **ANCHOR MAP:** signed by design QA — `CREATED BY STEP 8`
- **FIDELITY CHECK:** this lane's own copy of the measured checker, taken from the shape at `projects/personal/learning-app/standard/fidelity-check.mjs` and driven through the shared rig `projects/shared-tooling/browser.mjs` under the machine-wide lock — `CREATED BY STEP 8`; selftest → `0 · 0`, sabotage → red, shown red before it is trusted
- **VIEWPORTS AND THEMES:** desktop 1440 and phone 390, light and dark — the same four the rest of the Hub is measured at
- **RULE:** the screen's definition of done is `mismatched properties: 0 · unmeasured anchors: 0` at every viewport × theme, reproduced once by the step's checker, then graded once by the creative director. A non-zero count loops the builder; it never summons a second grader. The Hub's own tokens win where the drawing and the tokens disagree, recorded as a signed difference in the anchor map.

## 3 · Lanes and frozen contracts

| Lane | Scope (in / out) | Owner | Definition of done | Builder (cheap, named) | Backup builder | Checker (different model) | Backup checker |
|---|---|---|---|---|---|---|---|
| Finding and watching | the saved description of who to reach, the people search, the list import, the post watch and the substance filter / out: any login-holding reader | this lane | L1, L2 pass and the filter's four fixtures classify 4 of 4 | Qwen | DeepSeek | GLM 5.3 (zai) | Sonnet |
| Writing and refusing | the candidate drafter and the machine-writing refusal / out: the client's posts and the client's voice file | this lane | every candidate names something specific and takes a position; a known-tell fixture is refused | Qwen | GLM 5.3 (zai) | DeepSeek | Sonnet |
| Approving and posting | one new proposal kind and its effect module, the paired browser profile, the timing window and the ceiling / out: a second approval system, a second browser rig | this lane | L3 to L7 pass with LinkedIn's own copy read back | GLM 5.3 (zai) | DeepSeek | Qwen | Sonnet |
| Seeing what it did | the results read-back, the bias it applies, the LinkedIn screen / out: any restyle of the rest of the Hub | this lane | L8 and L9 pass at zero | GLM 5.3 (zai) | Qwen | DeepSeek | Sonnet |
| Outreach | the connection note, the acceptance watch and the direct message, all on the same spine / out: bulk anything | this lane | L10 and L11 pass with one click per item | Qwen | DeepSeek | GLM 5.3 (zai) | Sonnet |
| Polish | coverage of every state at every size and theme, and close-out / out: anything new | this lane | STEP 10 and STEP 11 closed | Qwen | DeepSeek | GLM 5.3 (zai) | Sonnet |

**Contracts between lanes (FROZEN at plan time — change = dated PLAN-CHANGES.md delta):** every read of LinkedIn goes through the sanctioned people and posts endpoints and never through a login-holding reader · every write to LinkedIn goes through ONE paired browser profile owned by this lane, under the machine-wide Chrome lock, and never through a second rig · every outbound item is staged as ONE proposal in the queue that already exists and is decided by Nick's signed session only; this lane adds one KIND and never a second queue · this lane writes exactly one new key in the Hub's key-value store, through the existing read-modify-write helper, and never touches the prospect store, the client roster or the contact list, which the agent system spec owns · the CAPTUS lane owns the client's posts, the client's voice file and the client approval screen, and this lane changes none of them · the SCHEDULED lane owns every clock and receives this lane's cadence by a handoff line · a comment's text, once approved, is the text that posts, character for character, and is read back from LinkedIn to prove it.

**Data floor, binding:** the only reasons a file stays inside are a login, a credential or token or key VALUE, a government ID, or a card, bank or routing number — and the refuser must prove the hit. The target list, the posts, the drafted comments, the connection notes and the results are none of those and they travel to cheap vendors freely. Nick's LinkedIn login is on that list and never appears in any file this build creates: the paired profile holds a browser cookie jar and no code path in this lane types or reads a credential, exactly as the shopping browser already works. A wall refusing anything else is logged as a failure in PROGRESS.txt and the job goes to the named backup vendor, never to Sonnet or Fable.

## 3b · Execution map — FRONT first, POLISH last, one row per step

A task is DONE only when its review-ledger row is CLOSED by a reviewer that is not the builder.

**Step map (read this first) — FRONT rows are what Nick sees or uses; POLISH rows run after the FRONT rows close, or the moment one bites:**

| Stage | # | TIER | Task (step name) | FOR NICK | Needs (named artefact, or `none — start now`) | EXECUTOR (cheap model) | EXECUTOR BACKUP | CHECKER (different model) | CHECKER BACKUP | DONE-PROOF (runnable command) |
|---|---|---|---|---|---|---|---|---|---|---|
| Finding and watching | 1 | FRONT | A written description of exactly who you want to reach becomes a real named list of people, and a hand-picked list can be dropped in instead | you describe the person you want in front of and get back real names, not a category | none — start now | Qwen | DeepSeek | GLM 5.3 (zai) | Sonnet | `node <the lane's target-list job> --dry` prints `targets: description saved · titles n · locations n · dry run accepted · debit 0 · would return up to 50` and refuses with `no description saved` when none exists |
| Finding and watching | 2 | FRONT | Every person on the list is watched for new posts, and the posts with nothing to answer are thrown away | only posts worth replying to ever reach you | STEP 1's saved description and its list | DeepSeek | GLM 5.3 (zai) | Qwen | Sonnet | `node <the lane's watch job> --selftest` prints `PASS li-watch selftest 4 of 4` classifying a reshare, a one-liner, an engagement-bait post and a real post correctly |
| Writing and refusing | 3 | FRONT | Two or three comments per post, each naming something specific in that post and taking a position; agreement-only is refused by the machine | the comments waiting for you say something, instead of saying nothing politely | STEP 2's kept posts | Qwen | GLM 5.3 (zai) | DeepSeek | Sonnet | `node <the lane's candidate check> --fixtures` prints `candidates: 3 · specific reference 3 of 3 · position taken 3 of 3 · praise-only refused 2 of 2` |
| Writing and refusing | 4 | FRONT | Anything that reads like a machine wrote it never reaches your screen, and the checker is shown catching one before it is trusted | you never read a comment that sounds like a robot, because it was thrown away first | STEP 3's candidates | DeepSeek | Qwen | GLM 5.3 (zai) | Sonnet | `node <the lane's anti-slop check> --red-then-green` prints `anti-slop: machine fixture REFUSED · human fixture PASSED · live candidates refused n of m` |
| Approving and posting | 5 | FRONT | One card, three buttons: approve, edit and approve, or skip — and nothing at all can reach LinkedIn without your click | you clear the day's comments in a minute and nothing goes out that you did not press | STEP 4's surviving candidates | GLM 5.3 (zai) | DeepSeek | Qwen | Sonnet | `node <the lane's engagement journey> --approve` prints `approve: card shown · approved 1 · edited 1 · skipped 1 · robot decide refused 401 · queue read back 3 of 3` |
| Approving and posting | 6 | FRONT | An approved comment appears under the real post between twenty and ninety minutes after it went live, at a randomised time, inside a hard daily ceiling | your comments land when they actually get seen, and never in a burst that puts your account at risk | STEP 5 closed and §7 item 2 (the browser paired) | GLM 5.3 (zai) | DeepSeek | Qwen | Sonnet | `node <the lane's engagement journey> --post-own` prints `post: 1 posted on Nick's own post · LinkedIn copy hash equal · fired at +NN minutes inside 20-90 · today n of ceiling · unapproved posted 0` |
| Seeing what it did | 7 | FRONT | The replies, reactions and profile views each comment earned are read back, and the next drafts lean toward what earned them | you can see which comments actually started a conversation, and the next ones look more like those | STEP 6 closed | DeepSeek | Qwen | GLM 5.3 (zai) | Sonnet | `node <the lane's results job> --once --dry` prints `results: comments n · replies n · reactions n · profile views n · bias rows written n` and the same numbers read back through the Hub |
| Seeing what it did | 8 | FRONT | One Hub screen: who is on the list, how many went out today, and what each comment did — measured at zero against its own drawing | you open one screen and see the whole thing at a glance | STEP 7 closed and the drawing approved | GLM 5.3 (zai) | Qwen | DeepSeek | Sonnet | `node <the lane's fidelity check> --screen linkedin --out <the lane's evidence folder>` prints `mismatched properties: 0 · unmeasured anchors: 0` for all four cells |
| Outreach | 9 | FRONT | Connection notes and direct messages on the same spine: drafted, refused if they sound like a machine, one click each, and no message before a connection is accepted | you reach the people whose posts you commented on, one click at a time, in your own words | §7 item 3 answered, and STEP 6 closed | Qwen | DeepSeek | GLM 5.3 (zai) | Sonnet | `node <the lane's engagement journey> --outreach` prints `outreach: 1 note approved and sent · LinkedIn copy read back · unapproved sent 0 · message before acceptance 0 · today n of ceiling` |
| Polish | 10 | POLISH | Coverage: every state above opened once at both widths in both themes, every gap signed with a reason | nothing you notice; every screen was opened once in every size and theme | STEP 8 closed | Qwen | DeepSeek | GLM 5.3 (zai) | Sonnet | `node <the lane's engagement journey> --coverage` prints a table with every §2 cell observed and `unsigned gaps: 0` |
| Polish | 11 | POLISH | Close-out: the finish line checked item by item, the postmortem written into this file, the paired browser profile and every scratch file declared or removed | you get one line saying the LinkedIn lane is done, and nothing else to read | STEP 1 to STEP 10 closed | GLM 5.3 (zai) | DeepSeek | Qwen | Sonnet | `python3 projects/ops/agents/check_plan.py --progress projects/ops/life-os/REGROUP-2026-09-08/plans/LINKEDIN-ENGINE/PLAN.proposed.txt` prints every §3b row VERIFIED |

### §3c · CUT — considered for this lane, overkill for the outcome, recorded once and not worked
- A login-holding LinkedIn reader of our own for richer profile fields — the sanctioned people and posts endpoints answer the question, and the account's safety is worth more than the extra fields.
- The hosted humanizer service as a required gate — it has never run, its key is refused for lack of scope, and the one real comparison of it recommends against treating a pass as an improvement; the checklist that does run is the gate, and the service is §7 item 4.
- A daily batch review screen for v1 — the approval card Nick already reads is the surface, and STEP 8's screen is where a batch view lands later without any change of mechanism.
- Multi-account and per-client support — §1a row 2; the code leaves room and this lane builds none of it.
- Enriching a target into an email address and a phone number — that is the Lead Generator's territory in `projects/ops/agents/AGENT-SYSTEM-SPEC.md`, and this lane has no path to the contact list at all.
- A separate "engagement metrics" store — the Hub's key-value store holds it under this lane's one new key, read through the helper that already exists.

**Then one block per step, in this exact shape:**

### STEP 1 — Who you actually want to reach, turned into real names
**FOR NICK:** you write down exactly the kind of person you want in front of — their job, their industry, where they are — and a real named list comes back, or you hand over a list you picked yourself. · **Tier:** FRONT
**Start when:** none — start now.
**Builder:** Qwen · **Builder backup:** DeepSeek · **Checker:** GLM 5.3 (zai), a different session · **Checker backup:** Sonnet
**Files you may touch:** a new job under `projects/ops/skippy-jobs/jobs/` and its sibling state file, and a new key-value key written through `projects/business/business-app/app/functions/api/_kv.js` by a new door under `projects/business/business-app/app/functions/api/`. **Never** `projects/ops/skippy-jobs/jobs/captus-tracker-refresh.mjs` or `projects/ops/skippy-jobs/jobs/captus-voice-feedback.mjs` (the CAPTUS lane), the prospect store or the contact list (the agent system spec), `projects/ops/skippy-jobs/runner.mjs` (the SCHEDULED lane, STEP 6's handoff).

**Do exactly this:**
1. The overseer writes the job skeleton and every network call by hand, because the cheap lane holds no shell and cannot add a network call to a new file; the cheap builder fills the marked pure functions. Follow the construction of `projects/ops/skippy-jobs/jobs/captus-tracker-refresh.mjs` exactly: a header carrying the measured API contract, exported pure functions, one `runOnce({dry})`, an offline `selftest()`, and a guarded direct-run.
2. Store the description of who Nick wants to reach as one saved, named record: job titles, industries, locations, company attributes, and a recent-activity signal. A run with no saved record refuses in words and does not guess — the same rule the agent system spec already applies to its own target definitions.
3. Call `POST $DEEPAPI_API_BASE_URL/v1/scrape/linkedin/people` with `titles`, `locations`, `companies` or `query` from that record, `maxItems: 50`, `includeDetails: true`, an `Idempotency-Key` header, and `Authorization: Bearer $DEEPAPI_API_KEY` read from `/Users/nickdeck/.deepapi/env`. Fan out one call per titles-by-locations variant rather than one broad call. Follow the polling `next` whenever it is present, whatever the status.
4. `--dry` sets `dryRun: true` and asserts the answer comes back `status: dry_run` with `debitMicrousd: 0`, so the whole path can be exercised at zero spend.
5. Accept a hand-picked list of profile addresses from a file and merge it into the same list, marked as hand-picked, deduplicated by profile address.
6. Write the list to the lane's one new key-value key through `rmw` in `projects/business/business-app/app/functions/api/_kv.js`, with an idempotent mutator that contains no network call.

**DEFINITION OF DONE:** a saved description of who Nick wants to reach produces a named list of real people at zero spend on a dry run, a hand-picked list merges into the same list, and a run with no saved description refuses instead of guessing.
**PROOF:** `node <the lane's target-list job> --dry` → `targets: description saved · titles n · locations n · dry run accepted · debit 0 · would return up to 50`, and the same command with the description removed → `no description saved` · **FAILS IF:** the dry run debits anything, a run without a description returns rows, or a hand-picked address is dropped or duplicated

**If the check fails:** the builder fixes and re-checks the named failure until it passes. If this step cannot close from this machine: one line to the overseer naming the ONE missing thing, then the next step whose inputs exist.
**Checker's job:** re-run the PROOF yourself, once. PASS closes the step. Do not accept the builder's pasted output; do not summon anyone else.
**Handoff (if any):** none.

### STEP 2 — Watching them, and throwing away the posts with nothing in them
**FOR NICK:** only posts with something real in them ever reach you — reshares, one-liners and bait are gone before you see them. · **Tier:** FRONT
**Start when:** STEP 1's saved description and its list exist in the lane's key-value key.
**Builder:** DeepSeek · **Builder backup:** GLM 5.3 (zai) · **Checker:** Qwen, a different session · **Checker backup:** Sonnet
**Files you may touch:** the lane's watch job under `projects/ops/skippy-jobs/jobs/` and its sibling state file, and the lane's key-value door under `projects/business/business-app/app/functions/api/`. **Never** the CAPTUS jobs, `projects/ops/skippy-jobs/runner.mjs`, or STEP 1's target-list job.

**Do exactly this:**
1. For each profile on the list, call `POST $DEEPAPI_API_BASE_URL/v1/scrape/linkedin/posts` with body `{profiles:[<profile url>], maxItems, since}` and an `Idempotency-Key` header — the shape already measured on this machine and recorded in the header of `projects/ops/skippy-jobs/jobs/captus-tracker-refresh.mjs`. Read the response defensively the same way that file's `metricsFromOutput()` does: the field names are not guaranteed.
2. Keep the state of what has already been seen in the job's own sibling state file, so a re-run does not re-offer a post.
3. Write the substance filter as a rule with four named fixtures committed beside it in the lane's evidence folder: a reshare with no added words, a one-liner, an engagement-bait post ("agree?", "comment YES"), and a real post carrying a number and a claim. The filter keeps only the fourth.
4. Record for every kept post: its activity id, its author, its full text, when it went live, and the twenty-to-ninety-minute window that STEP 6 will fire inside.
5. Store the kept posts under the lane's key through the same read-modify-write helper.

**DEFINITION OF DONE:** every person on the list is checked for new posts on one pass, the same post is never offered twice, and the four fixtures are classified 4 of 4 with only the real post kept.
**PROOF:** `node <the lane's watch job> --selftest` → `PASS li-watch selftest 4 of 4` · **FAILS IF:** any fixture is misclassified, a post already seen is offered again, or the run writes anything when the endpoint returned a blocked or failed status

**If the check fails:** the builder fixes and re-checks the named failure until it passes. If this step cannot close from this machine: one line to the overseer naming the ONE missing thing, then the next step whose inputs exist.
**Checker's job:** re-run the PROOF yourself, once. PASS closes the step. Do not accept the builder's pasted output; do not summon anyone else.
**Handoff (if any):** none.

### STEP 3 — Comments that actually say something
**FOR NICK:** the comments waiting for you name a real number, claim or example from the post and take a side — never "great post". · **Tier:** FRONT
**Start when:** STEP 2's kept posts exist in the lane's key-value key.
**Builder:** Qwen · **Builder backup:** GLM 5.3 (zai) · **Checker:** DeepSeek, a different session · **Checker backup:** Sonnet
**Files you may touch:** the lane's drafting job under `projects/ops/skippy-jobs/jobs/`, the lane's candidate check under the lane folder, and the lane's evidence folder. **Never** `ZION/skills/ghostwrite/SKILL.md`, `ZION/skills/jasmin-brief/SKILL.md` or any client voice file under `projects/business/marketing-sales/clients/` — the CAPTUS lane and the SKILLS lane own those.

**Do exactly this:**
1. For each kept post, gather three inputs: the post's full text, that author's recent posts already fetched in STEP 2, and that author's headline and biography from `POST $DEEPAPI_API_BASE_URL/v1/scrape/linkedin/profile`.
2. Generate two or three candidates. Each one must do two things and the drafter must record which: name a SPECIFIC element of that post — a number, a named claim, or a concrete example, quoted — and take one of four positions: agree with a reason, disagree with a reason, add a concrete example, or ask a sharp question.
3. Refuse any candidate that does neither. Praise-only and agreement-only candidates are dropped by the rule, not by taste, and the count of dropped candidates is written into the run's line.
4. Keep every candidate under 1,200 characters, in short paragraphs, matching the house shape already written down in `ZION/skills/ghostwrite/SKILL.md` — read it, do not edit it.
5. Commit the fixtures the check runs on into the lane's evidence folder: three posts and their expected outcomes, plus two praise-only candidates that must be refused.

**DEFINITION OF DONE:** every candidate that survives quotes a specific element of the post it answers and takes one of the four positions, and a praise-only candidate is refused by the machine.
**PROOF:** `node <the lane's candidate check> --fixtures` → `candidates: 3 · specific reference 3 of 3 · position taken 3 of 3 · praise-only refused 2 of 2` · **FAILS IF:** any surviving candidate carries no quoted element of its post, any takes no position, or a praise-only fixture survives

**If the check fails:** the builder fixes and re-checks the named failure until it passes. If this step cannot close from this machine: one line to the overseer naming the ONE missing thing, then the next step whose inputs exist.
**Checker's job:** re-run the PROOF yourself, once, on your own copy of the fixtures. PASS closes the step. Do not accept the builder's pasted output; do not summon anyone else.
**Handoff (if any):** none.

### STEP 4 — Nothing that reads like a machine reaches your screen
**FOR NICK:** you never read a comment that sounds like a robot wrote it, because it was thrown away before it got to you. · **Tier:** FRONT
**Start when:** STEP 3's candidates exist.
**Builder:** DeepSeek · **Builder backup:** Qwen · **Checker:** GLM 5.3 (zai), a different session · **Checker backup:** Sonnet
**Files you may touch:** the lane's anti-slop check under the lane folder and the lane's evidence folder. **Never** `ZION/skills/nicksvoice/SKILL.md` or anything under `ZION/skills/` — the SKILLS lane owns them; this step READS the checklist and copies nothing.

**Do exactly this:**
1. Read `ZION/skills/nicksvoice/SKILL.md` and its four reference files, and take the pattern list from there rather than writing a new one. That checklist is the instrument that actually runs in this workspace today.
2. Run every surviving candidate through the list one pattern at a time: overused dashes, the "it's not just X, it's Y" shape, forced balance, listy phrasing, stock words, inflated claims, and the rest of the list as written.
3. A candidate that trips a pattern is either rewritten once by the same builder or dropped; it is never shown to Nick carrying the tell.
4. Prove the checker can fail before anyone trusts it: commit two fixtures into the lane's evidence folder — one deliberately written with the tells, one written by a person — and show the first refused and the second passed in the same run.
5. Write the refused count into the run's own line, so a checker that suddenly refuses nothing is visible rather than silent.

**DEFINITION OF DONE:** every candidate Nick sees has passed the checklist, and the checker is shown refusing a known-machine fixture and passing a known-human one in the same run.
**PROOF:** `node <the lane's anti-slop check> --red-then-green` → `anti-slop: machine fixture REFUSED · human fixture PASSED · live candidates refused n of m` · **FAILS IF:** the machine fixture passes, the human fixture is refused, or the live refused count is absent from the line

**If the check fails:** the builder fixes and re-checks the named failure until it passes. If this step cannot close from this machine: one line to the overseer naming the ONE missing thing, then the next step whose inputs exist.
**Checker's job:** re-run the PROOF yourself, once. PASS closes the step. Do not accept the builder's pasted output; do not summon anyone else.
**Handoff (if any):** none.

### STEP 5 — One card, three buttons, and nothing moves without your click
**FOR NICK:** you clear the day's comments in about a minute, and nothing whatsoever reaches LinkedIn that you did not press. · **Tier:** FRONT
**Start when:** STEP 4's surviving candidates exist.
**Builder:** GLM 5.3 (zai) · **Builder backup:** DeepSeek · **Checker:** Qwen, a different session · **Checker backup:** Sonnet
**Files you may touch:** a new import-only effect module under `projects/business/business-app/app/functions/api/` named with a leading underscore so it is never routed, and exactly three lines in `projects/business/business-app/app/functions/api/proposals.js` — the import, the daily-cap exemption, and the post-commit effect hook. **Never** `projects/business/business-app/app/functions/api/_captus-draft.js` (the CAPTUS lane), `projects/business/business-app/app/js/proposals.js` (the queue card, owned by the HUB lane and reused unchanged), `projects/business/business-app/app/functions/api/_session.js`.

**Do exactly this:**
1. Copy the shape of `projects/business/business-app/app/functions/api/_captus-draft.js` exactly: export the kind constant and one `apply...Decision(env, snapshot, action, declineReason, identity)`.
2. Touch `proposals.js` in three places only, in the same positions the Captus kind already occupies: the import, the daily-cap exemption, and the effect hook after the commit.
3. Stage one proposal per surviving candidate, deduplicated on the post's activity id so a re-run never produces a second card for the same post. The card's first line says exactly what will happen: the words that will post, under whose post, and when.
4. Map the three verbs the existing card already offers: approve, edit-and-approve, decline — approve, edit, skip. On edit-and-approve, the words Nick typed are stored as the words that will post, character for character, with the original kept beside them.
5. Make the effect idempotent by a per-proposal marker on the item, exactly as the Captus effect does, so a retry can never queue a second post.
6. Change nothing in the queue card component; the card renders through it as it stands.
7. Publish through the Hub's own pipeline (`node projects/ops/deploy.mjs deck-business --use-staged-dist`) and read the served bytes back.

**DEFINITION OF DONE:** each surviving candidate appears once as a card Nick can approve, edit or skip; the decision writes an approved item that STEP 6 can post; and a decision arriving with a robot token is refused with 401 and writes nothing.
**PROOF:** `node <the lane's engagement journey> --approve` → `approve: card shown · approved 1 · edited 1 · skipped 1 · robot decide refused 401 · queue read back 3 of 3` · **FAILS IF:** a robot decision succeeds, a second card appears for one post, an edited comment's stored words differ from what was typed, or a skipped card comes back on reload

**If the check fails:** the builder fixes and re-checks the named failure until it passes. If this step cannot close from this machine: one line to the overseer naming the ONE missing thing, then the next step whose inputs exist.
**Checker's job:** re-run the PROOF yourself, once, against the live Hub. PASS closes the step. Do not accept the builder's pasted output; do not summon anyone else.
**Handoff (if any):** the moment this step closes, post one dated line into `projects/ops/life-os/REGROUP-2026-09-08/plans/HUB/PLAN.proposed.txt`: `STEP 5 closed <date> — the Inbox now carries one more proposal kind; the queue card component was reused unchanged and nothing in the Inbox's own behaviour moved.`

### STEP 6 — It posts at a human moment, at a human pace
**FOR NICK:** your approved comments land under those posts when people are actually reading them, and never in a burst that puts your account at risk. · **Tier:** FRONT
**Start when:** STEP 5 closed, and §7 item 2 answered — the browser profile paired.
**Builder:** GLM 5.3 (zai) · **Builder backup:** DeepSeek · **Checker:** Qwen, a different session · **Checker backup:** Sonnet
**Files you may touch:** the lane's posting job under `projects/ops/skippy-jobs/jobs/` and its sibling state file, and the lane's own browser module under the lane folder. **Never** `projects/personal/skippy-app/cart/browser.mjs` or `projects/personal/skippy-app/cart/pair.mjs` (the shopping session — read them, copy the pattern, change nothing), `projects/shared-tooling/browser.mjs` (shared, read-only here), `projects/ops/skippy-jobs/runner.mjs` (the SCHEDULED lane).

**Do exactly this:**
1. Build the session holder on the pattern that already works here: a dedicated persistent profile directory owned by this lane, paired ONCE by Nick himself through the lane's own pairing entry point, with no code path in this lane that types or reads a credential — read `projects/personal/skippy-app/cart/pair.mjs` and follow it. Take the machine-wide Chrome lock from `projects/ops/skippy-jobs/lib/with-chrome-lock.mjs` around every launch.
2. Guard the profile the way the shopping browser guards checkout: this lane's browser may navigate only to a post it holds an approved item for, and to nothing else; any other address is refused before navigation.
3. Draw each item's firing time fresh: a uniform random minute between 20 and 90 after that post went live, recomputed per item and never a fixed offset. Write the drawn time into the item before waiting, so the check can read what was chosen.
4. Enforce the ceiling as a refusal, not a setting: a hard maximum per day per action type, in the code, with no environment variable and no flag that raises it. At the ceiling the job refuses, says so in its line, and leaves the item for tomorrow.
5. Post the stored words exactly, then read the comment back from LinkedIn itself and compare a SHA-256 of the posted text with the approved text. A mismatch is a failure, not a warning.
6. Prove it on Nick's own post first — a comment by Nick on a post by Nick is not a message to another human, and it is the honest way to exercise the whole path before any real person is involved.
7. Write the day's counts and the drawn times into the job's sibling state file.

**DEFINITION OF DONE:** one approved comment is posted on Nick's own post through the paired session, between 20 and 90 minutes after that post went live at a time drawn for that item, LinkedIn's own copy reads back with an identical hash, the day's count is recorded, and an item that was never approved is never posted.
**PROOF:** `node <the lane's engagement journey> --post-own` → `post: 1 posted on Nick's own post · LinkedIn copy hash equal · fired at +NN minutes inside 20-90 · today n of ceiling · unapproved posted 0` · **FAILS IF:** the hashes differ, the offset falls outside 20-90 or repeats a previous day's value, the ceiling can be passed by any flag or variable, or anything unapproved reaches LinkedIn

**If the check fails:** the builder fixes and re-checks the named failure until it passes. If this step cannot close from this machine: one line to the overseer naming the ONE missing thing, then the next step whose inputs exist.
**Checker's job:** re-run the PROOF yourself, once. PASS closes the step. Do not accept the builder's pasted output; do not summon anyone else.
**Handoff (if any):** the moment this step closes, post one dated line into `projects/ops/life-os/REGROUP-2026-09-08/plans/SCHEDULED/PLAN.proposed.txt`: `STEP 6 closed <date> — this lane's watch, draft and post jobs are ready for a slot; they are not registered in the runner and nobody has put them on a clock.`

### STEP 7 — What each comment actually did
**FOR NICK:** you can see which comments started a conversation, and the next ones look more like the ones that did. · **Tier:** FRONT
**Start when:** STEP 6 closed.
**Builder:** DeepSeek · **Builder backup:** Qwen · **Checker:** GLM 5.3 (zai), a different session · **Checker backup:** Sonnet
**Files you may touch:** the lane's results job under `projects/ops/skippy-jobs/jobs/` and its sibling state file, the lane's drafting job (the bias it reads), and the lane's key-value door. **Never** `projects/ops/skippy-jobs/jobs/captus-tracker-refresh.mjs` (the CAPTUS lane's numbers job — read it for the pattern, change nothing).

**Do exactly this:**
1. For every posted comment, read back the replies and reactions it earned. Where a number is not publicly readable, say so in the line and leave the field honestly empty rather than reporting a zero — the Captus tracker already does exactly this for views, and it is the reason its numbers can be trusted.
2. Record whether that post's author looked at Nick's profile or connected afterwards, from the profile-views record available to the signed-in session; where it is not available, name it as unread rather than as none.
3. Write the results under the lane's key through the read-modify-write helper, keyed by date, so a second run of the same day updates in place and never appends a duplicate.
4. Feed it back: the drafting job reads the results and biases the position it picks and the shape it writes toward the candidates that earned replies. Write the number of bias rows into the run's own line so a loop that has quietly stopped learning is visible.
5. `--dry` prints and writes nothing.

**DEFINITION OF DONE:** every posted comment carries its replies, reactions and profile-view outcome with the date and the source they were read from, an unreadable number reads as unread rather than as zero, and the drafting job demonstrably reads them.
**PROOF:** `node <the lane's results job> --once --dry` → `results: comments n · replies n · reactions n · profile views n · bias rows written n`, and the same numbers read back through the Hub · **FAILS IF:** an unreadable number is written as zero, a second run of the same day appends a duplicate row, or the bias count is zero while results exist

**If the check fails:** the builder fixes and re-checks the named failure until it passes. If this step cannot close from this machine: one line to the overseer naming the ONE missing thing, then the next step whose inputs exist.
**Checker's job:** re-run the PROOF yourself, once. PASS closes the step. Do not accept the builder's pasted output; do not summon anyone else.
**Handoff (if any):** none.

### STEP 8 — One screen: the list, the pace, the results
**FOR NICK:** you open one screen and see who you are chasing, how much went out today, and what each comment did. · **Tier:** FRONT
**Start when:** STEP 7 closed, and the drawing this screen is measured against approved.
**Builder:** GLM 5.3 (zai) · **Builder backup:** Qwen · **Checker:** DeepSeek, a different session; Sienna grades once after the count is zero · **Checker backup:** Sonnet
**Files you may touch:** `projects/business/business-app/app/js/app.js` (the route id, the title, the tab visibility), `projects/business/business-app/app/index.html` (one new section and one nav tab and one script tag), a new `projects/business/business-app/app/js/` module ending in its own `BZ.register`, a new `projects/business/business-app/app/css/` stylesheet, the lane's own drawing generator and fidelity check under the lane folder. **Never** `projects/business/business-app/app/js/inbox.js` or `projects/business/business-app/app/js/proposals.js` (the HUB lane), the Hub design tokens, `projects/business/business-app/app/build-dist.js` (no new top-level page is added — this screen is a section of the existing shell).

**Do exactly this:**
1. Write the drawing first: a generated LinkedIn-screen page in the Hub's own tokens, published login-free beside its generator, and put it in front of Nick for one word of approval — the one sanctioned wait, and only for this first publish.
2. Machine-write the drawing's `shasum -a 256` into the lane's evidence folder, and write the anchor map beside the generator with every anchor mapped to a real selector on the built screen.
3. Register the screen the way every other Hub screen is registered: the route id in `VIEWS`, the title in `TITLES`, a visibility entry restricting it to Nick, one hidden section in the shell following the `#view-artifacts` pattern, one hidden nav tab, one script tag, one stylesheet link.
4. Render three things and nothing else: the target list with its saved description and the Import control; today's pace against the ceiling; and one row per comment with its result and the date it was read.
5. Build the lane's fidelity check from the shape at `projects/personal/learning-app/standard/fidelity-check.mjs`, driven through `projects/shared-tooling/browser.mjs` under the lock. Show it red on a deliberately broken copy before trusting a zero.
6. Publish through the Hub's own pipeline and read the served bytes back; run the check at 1440 and 390, light and dark, until every cell reads zero.

**DEFINITION OF DONE:** the LinkedIn screen on the live Hub matches its approved drawing at `mismatched properties: 0 · unmeasured anchors: 0` in all four cells, shows the list, the pace and the results, and is visible to Nick and to no other identity.
**PROOF:** `node <the lane's fidelity check> --screen linkedin --out <the lane's evidence folder>` → `mismatched properties: 0 · unmeasured anchors: 0` for all four cells · **FAILS IF:** any cell is non-zero, the check was never shown failing on a broken copy, or another identity can open the screen

**If the check fails:** the builder fixes and re-checks the named failure until it passes. If this step cannot close from this machine: one line to the overseer naming the ONE missing thing, then the next step whose inputs exist.
**Checker's job:** re-run the PROOF yourself, once, into your own folder. PASS closes the step. Do not accept the builder's pasted output; do not summon anyone else.
**Handoff (if any):** none.

### STEP 9 — Connection notes and messages, one click each
**FOR NICK:** you reach the people whose posts you already commented on, one at a time, in your own words, and no message ever goes before they accept. · **Tier:** FRONT
**Start when:** §7 item 3 answered — Nick's go on the outreach stage — and STEP 6 closed.
**Builder:** Qwen · **Builder backup:** DeepSeek · **Checker:** GLM 5.3 (zai), a different session · **Checker backup:** Sonnet
**Files you may touch:** the lane's outreach job under `projects/ops/skippy-jobs/jobs/` and its sibling state file, the lane's effect module under `projects/business/business-app/app/functions/api/`, and the lane's browser module. **Never** `projects/business/business-app/app/functions/api/proposals.js` beyond the same three lines STEP 5 owns, the contact list or the prospect store, `projects/personal/skippy-app/wa/` (the messaging session, a different lane).

**Do exactly this:**
1. Draft a connection note that names the same specific thing about that person the comment engine already found — the quoted element from their post — and nothing generic.
2. Run every note through STEP 4's refusal before it is staged, exactly as a comment is.
3. Stage each note as one card of the same kind, one person per card, and send exactly one note on Nick's click. Never batch, never a standing approval for a person, never a standing approval for a list.
4. Watch for acceptance. A direct message is drafted only for a person whose connection has been accepted; the drafter refuses otherwise and the refusal is counted in the line, so the impossibility is measured rather than asserted.
5. Apply the same daily ceiling, drawn timing and read-back that STEP 6 built — one implementation, extended, never a second.
6. Read LinkedIn's own copy of the sent note back and compare hashes, the same way a comment is proven.

**DEFINITION OF DONE:** one connection note is sent on Nick's own click, its copy reads back from LinkedIn identically, nothing unapproved is sent, no direct message exists for an unaccepted connection, and the day's count is inside the ceiling.
**PROOF:** `node <the lane's engagement journey> --outreach` → `outreach: 1 note approved and sent · LinkedIn copy read back · unapproved sent 0 · message before acceptance 0 · today n of ceiling` · **FAILS IF:** anything is sent without its own click, a direct message is drafted before acceptance, the ceiling is passable, or the sent copy differs from the approved copy

**If the check fails:** the builder fixes and re-checks the named failure until it passes. If this step cannot close from this machine: one line to the overseer naming the ONE missing thing, then the next step whose inputs exist.
**Checker's job:** re-run the PROOF yourself, once. PASS closes the step. Do not accept the builder's pasted output; do not summon anyone else.
**Handoff (if any):** none.

### STEP 10 — Every screen opened once, at every size, in every theme
**FOR NICK:** nothing you notice; every state of every screen was opened once in every size and theme. · **Tier:** POLISH
**Start when:** STEP 8 closed.
**Builder:** Qwen · **Builder backup:** DeepSeek · **Checker:** GLM 5.3 (zai), a different session · **Checker backup:** Sonnet
**Files you may touch:** the lane's engagement journey under the lane folder and the lane's evidence folder. **Never** any Hub product file — this step observes and changes nothing.

**Do exactly this:**
1. Add a coverage mode to the lane's journey that walks every row of §2 at 1440 and 390, light and dark, signed in as Nick.
2. Record every cell as observed, or as a gap with a written reason; an unsigned gap fails the run.
3. Save the table and the captures into the lane's evidence folder.

**DEFINITION OF DONE:** every §2 row is observed at both widths in both themes with zero unsigned gaps.
**PROOF:** `node <the lane's engagement journey> --coverage` → a table with every §2 cell observed and `unsigned gaps: 0` · **FAILS IF:** any cell is missing, or a gap carries no written reason

**If the check fails:** the builder fixes and re-checks the named failure until it passes. If this step cannot close from this machine: one line to the overseer naming the ONE missing thing, then the next step whose inputs exist.
**Checker's job:** re-run the PROOF yourself, once, into your own folder. PASS closes the step. Do not accept the builder's pasted output; do not summon anyone else.
**Handoff (if any):** none.

### STEP 11 — Close-out
**FOR NICK:** you get one line saying the LinkedIn lane is done, and nothing else to read. · **Tier:** POLISH
**Start when:** STEP 1 to STEP 10 closed.
**Builder:** GLM 5.3 (zai) · **Builder backup:** DeepSeek · **Checker:** Qwen, a different session · **Checker backup:** Sonnet
**Files you may touch:** this file's POSTMORTEM and STEPS sections, `projects/ops/life-os/REGROUP-2026-09-08/plans/LINKEDIN-ENGINE/PROGRESS.txt`, `projects/ops/life-os/REGROUP-2026-09-08/plans/LINKEDIN-ENGINE/STEPS.json`. **Never** any product file.

**Do exactly this:**
1. Walk the FINISH LINE item by item and name the closed step that proves each.
2. Write the postmortem into this file's POSTMORTEM section: what took longer than expected, what the plan got wrong, and the one thing that would have saved the most time.
3. Declare what is left on the Mac and how big it is — the paired browser profile above all, which is the one thing this lane creates that does not belong in the cloud copy and must be named rather than forgotten.
4. Move the lane's board card to done through the one command that moves the card, the screen and the plan together.

**DEFINITION OF DONE:** every §3b row reads VERIFIED, the postmortem is written, and everything left on the machine is declared with its size.
**PROOF:** `python3 projects/ops/agents/check_plan.py --progress projects/ops/life-os/REGROUP-2026-09-08/plans/LINKEDIN-ENGINE/PLAN.proposed.txt` → every §3b row VERIFIED · **FAILS IF:** any row is unverified, or anything left on the machine is undeclared

**If the check fails:** the builder fixes and re-checks the named failure until it passes. If this step cannot close from this machine: one line to the overseer naming the ONE missing thing, then the next step whose inputs exist.
**Checker's job:** re-run the PROOF yourself, once. PASS closes the step. Do not accept the builder's pasted output; do not summon anyone else.
**Handoff (if any):** none.

## 4 · Regret Check (the registry failures this build is actually exposed to)

| Failure mode (registry entry) | The measure in THIS plan that prevents it | Where it lives (section / artifact / gate) |
|---|---|---|
| A second system was built because the first was invisible | the approval queue that already refuses robots is extended with ONE kind in three lines, the queue card is reused unchanged, the browser rig and its lock are the shared ones, and the results ride the store the Hub already reads | §3 contracts; STEP 5; STEP 6; §3c |
| Work was written to a queue no reader ever visits | every candidate lands as a card in the Inbox Nick already opens, and the consumer of every artefact is named before it is written | §5 artefact consumers; STEP 5 |
| A proposal was sold on a capability never opened and read | the people-search contract was fetched live and dry-run at zero spend, the posts endpoint's shape was taken from a caller measured on this machine, and the hosted browser's own safety block was read and found to refuse logins — none of the three was assumed | Already true; Gate Zero; §3c |
| An absence was asserted without opening the store that would hold it | the vault was listed (131 entries, zero LinkedIn), the runner was grepped (zero Captus rows), and the registry was read (38 rows, no owner for this) — each with the command and the count | Already true; Gate Zero ownership check |
| Output was delivered somewhere the intended reader never looks | every posted comment and every sent note is read back from LinkedIn's own copy and compared by hash before the step closes | STEP 6; STEP 9; §6 |
| Personal or identifying data written to the wrong subject | a comment is posted only against the activity id it was drafted for, the browser refuses to navigate anywhere else, and the effect is idempotent by a per-item marker | STEP 5 item 5; STEP 6 items 2 and 5 |
| Four builds in one night went to Sonnet or Fable because the cheap-lane walls refused files that hold nothing private | the floor is four items and the refuser proves the hit; every step names a cheap builder and a cheap backup; a refusal is logged as a failure, never a promotion | §3 data floor; STEP 0 item 3 |
| A check reported PASS while the thing it graded was wrong, because the instrument could not fail | the anti-slop checker and the fidelity checker are both shown RED on a deliberately broken fixture before any zero is believed, and the substance filter is proven on four fixtures including three it must reject | STEP 2 item 3; STEP 4 item 4; §2d |
| A gate that has never actually run was carried forward as a required step | the humanizer service named in the spec was re-measured, found blocked and negatively evaluated, and is named in §7 as an upgrade rather than written in as a dependency | §3c; §7 item 4; Gate Zero P3 |
| A ceiling was checked at start only, or made raisable by a setting | the daily ceiling is a refusal in code with no flag and no environment variable, and the proof fails if any flag can pass it | STEP 6 item 4; §1a row 5 |

## 5 · Topology and roles
- **OVERSEER-AUTHORITY:** none named in `projects/ops/OVERSEER-AUTHORITY.md` for this lane; the Group D overseer's word binds it. **The four approval classes (money leaving · credential rotation · irreversible destruction · a message sent as Nick) and the floor (logins · credentials, tokens and keys · government IDs · card, bank and routing numbers) never move on the overseer's word.** A comment by Nick on Nick's own post, used once to exercise the posting path, is not a message to another human; every comment or note aimed at anyone else is, and carries its own click.
- Thread layout: one Group D overseer thread for this lane; builders and checkers as cheap dispatches from it. The overseer writes every job skeleton and every network call by hand, because the cheap lane holds five file tools and no shell and cannot add a network call to a new file — the same division that already built both Captus jobs.
- Overseer: Fable, with Opus taking over in-thread at the Fable limit · Workers: GLM 5.3 (zai), DeepSeek, Qwen by step; Sonnet only as a backup checker · Cap: 8 per session, ~40 machine-wide, counted before each wave, and one at a time for anything launching a browser
- State files location: `projects/ops/life-os/REGROUP-2026-09-08/plans/LINKEDIN-ENGINE/PROGRESS.txt` (dated lines, newest last) and `projects/ops/life-os/REGROUP-2026-09-08/plans/LINKEDIN-ENGINE/STEPS.json` (the step record the progress screen reads)
- **Board card id:** none yet — the overseer creates the lane's card at pickup and writes its slug here in the same edit; every step close goes through `projects/ops/skippy-jobs/lib/unified-project-update.mjs`, never a board helper
- **Artefact consumers:** the target list → the watch job · the kept posts → the drafting job · the candidates → the anti-slop check → the proposal queue → Nick · the approved items → the posting job → LinkedIn · the results → the drafting job's bias and the LinkedIn screen · STEPS.json → the Hub progress screen · PROGRESS.txt → the morning report · the closed-step handoff lines → the HUB and SCHEDULED plan files · §7 → Nick, once.
- **Write-contention (parallel lanes in a shared checkout):** this lane writes only its own plan folder, its own jobs under `projects/ops/skippy-jobs/jobs/`, its own key-value door and effect module, the three named lines of `proposals.js`, and the Hub screen files STEP 8 names; scoped commits with pathspecs, never a bare commit; the checkout proven writable before the first dispatch. `proposals.js` and `app.js` are shared with the HUB lane and are touched in one small edit each, announced in PROGRESS.txt before the edit and committed in the same pass.

**Per-stage topology — counts DECLARED at plan time (machine-gated: a number in every row):**

| Stage | Overseer | Sub-overseers | Workers |
|---|---|---|---|
| Finding and watching | 1 | 0 | 2 |
| Writing and refusing | 1 | 0 | 2 |
| Approving and posting | 1 | 0 | 2 |
| Seeing what it did | 1 | 0 | 2 |
| Outreach | 1 | 0 | 2 |
| Polish | 1 | 0 | 2 |

**The walk-away contract — a stranger resumes the drive from files alone:**
- **STATE FILE:** `projects/ops/life-os/REGROUP-2026-09-08/plans/LINKEDIN-ENGINE/PROGRESS.txt`
- **HEARTBEAT ROW:** `linkedin-lane-2026-09-10` in `projects/personal/skippy-app/ala-state/work-threads.json`
- **MORNING-REPORT LINE:** "LinkedIn — FRONT <n> of 9 · polish <m> of 2" in `projects/ops/walkaway/REPORT.md`

## 6 · Evals — what "working" means, decided now

| Capability | Check (exact command or procedure) | Pass looks like |
|---|---|---|
| a written description becomes a named list | `node <the lane's target-list job> --dry` | the dry run is accepted, debit 0, and a run with no saved description refuses |
| a hand-picked list can be used instead | the same job with a list file, then read the stored list back | every hand-picked address present once, marked hand-picked |
| posts with nothing in them are discarded | `node <the lane's watch job> --selftest` | `PASS li-watch selftest 4 of 4` |
| every comment says something specific and takes a side | `node <the lane's candidate check> --fixtures` | specific reference 3 of 3, position taken 3 of 3, praise-only refused 2 of 2 |
| machine-sounding drafts never reach Nick | `node <the lane's anti-slop check> --red-then-green` | the machine fixture refused, the human fixture passed, a live refused count printed |
| nothing reaches LinkedIn without his click | `node <the lane's engagement journey> --approve` | approved 1, edited 1, skipped 1, robot decide refused 401 |
| it posts at a human moment inside a hard ceiling | `node <the lane's engagement journey> --post-own` | hash equal, offset inside 20-90, today's count under the ceiling, unapproved posted 0 |
| the replies are counted and fed back | `node <the lane's results job> --once --dry` | replies, reactions and profile views present with their dates, bias rows non-zero |
| one screen shows the list, the pace and the results | `node <the lane's fidelity check> --screen linkedin --out <the lane's evidence folder>` | zero and zero in all four cells |
| outreach travels the same loop, one click each | `node <the lane's engagement journey> --outreach` | one note sent and read back, unapproved sent 0, message before acceptance 0 |

## 7 · THE ONE DECISION LIST FOR NICK — everything genuinely his, asked once

Each item names the default that applies if he says nothing, so no lane waits.

1. **Move the posting side onto a rented cloud browser with a residential connection on your account, instead of running it from the Mac?** This is the only part of the build that spends money — a monthly hosting bill plus a connection fee. Default: it runs from the Mac on a browser profile you signed into once, which is exactly how the shopping browser already works and costs nothing. Say "rent it" to change that.
2. **One minute signing into LinkedIn once, in a browser window kept only for this.** No agent will ever type your password, so this one genuinely needs you. Default: nothing posts — approved comments queue up and wait.
3. **Turn the connection requests and direct messages on, after you have seen the comments?** Every single one is still one card and one click from you; this is only about whether they get drafted at all. Default: comments only, and nothing outbound is drafted or sent.
4. **Pay to unlock the writing-cleanup service the earlier notes name?** Its key is refused today for lack of the right access, and the one real test of it on nine posts rewrote quotes it was told to leave alone and invented a date. Default: it stays off; the writing checklist we already use is the gate.

Not asked, because you already answered: exactly four things need your yes and a message sent as you is the fourth, so every comment and every note is one click of yours; agents drive the real click paths and you are never the tester; nobody chases security or privacy; cheap models build and check everything here; a person collected from a search is never added to any bulk email list, ever.

## If you get stuck (all steps)

Before writing "blocked": (1) re-read the step's START WHEN line — most "stuck" is a misread gate, (2) try a concrete workaround, (3) write one line to the overseer naming the ONE missing artefact. Then keep working every other step whose inputs exist. Never idle on a blocker; never end a turn waiting on a background result.

## Your loop

Every pass: every FRONT step whose START WHEN inputs exist and which is not yet CLOSED is running, up to the cap → each builder runs its own PROOF, hands to its checker → PASS closes it, FAIL loops it → when the FRONT steps are closed, the POLISH steps run the same way → repeat until the FINISH LINE is proven.

## SUMMARY — a few plain-English lines, read by the status generator

Nothing of this exists yet. The pieces it leans on do: the search that finds the right people answers on this account, the way to read what those people post has already been used here, the card Nick approves things on already refuses to be approved by anything but him, and adding a screen to the Hub is a small, well-worn change. The one genuinely new thing is a browser that stays signed into LinkedIn, and even that has a working pattern here — the browser that already stays signed into his shopping sites, which he paired himself and which never handles a password in code. Nine visible steps first, in the order he would notice them, two polish steps after, cheap models building and checking, and nothing anywhere that can reach another person without his click.

## STEPS

1. Who you actually want to reach, turned into real names — 0%
   DEFINITION OF DONE: a saved description produces a named list at zero spend on a dry run, a hand-picked list merges in, and a run with no description refuses
   PROOF: `node <the lane's target-list job> --dry`
2. Watching them, and throwing away the posts with nothing in them — 0%
   DEFINITION OF DONE: every person is checked once a pass, no post is offered twice, and the four fixtures classify 4 of 4
   PROOF: `node <the lane's watch job> --selftest`
3. Comments that actually say something — 0%
   DEFINITION OF DONE: every surviving candidate quotes a specific element and takes one of the four positions; praise-only is refused
   PROOF: `node <the lane's candidate check> --fixtures`
4. Nothing that reads like a machine reaches your screen — 0%
   DEFINITION OF DONE: every candidate Nick sees passed the checklist, and the checker was shown refusing a machine fixture and passing a human one
   PROOF: `node <the lane's anti-slop check> --red-then-green`
5. One card, three buttons, and nothing moves without your click — 0%
   DEFINITION OF DONE: one card per candidate with approve, edit and skip; a robot decision refused 401; an edited comment stored character for character
   PROOF: `node <the lane's engagement journey> --approve`
6. It posts at a human moment, at a human pace — 0%
   DEFINITION OF DONE: an approved comment posts on Nick's own post inside a drawn 20-90 minute window, reads back with an equal hash, and the ceiling cannot be passed
   PROOF: `node <the lane's engagement journey> --post-own`
7. What each comment actually did — 0%
   DEFINITION OF DONE: replies, reactions and profile views carry their date and source, unreadable numbers read as unread, and the drafter reads them back
   PROOF: `node <the lane's results job> --once --dry`
8. One screen: the list, the pace, the results — 0%
   DEFINITION OF DONE: zero mismatched properties and zero unmeasured anchors in all four cells, visible to Nick alone
   PROOF: `node <the lane's fidelity check> --screen linkedin --out <the lane's evidence folder>`
9. Connection notes and messages, one click each — 0%
   DEFINITION OF DONE: one note sent on his own click and read back, nothing unapproved sent, no message before acceptance
   PROOF: `node <the lane's engagement journey> --outreach`
10. Every screen opened once, at every size, in every theme — 0%
   DEFINITION OF DONE: every §2 row observed at both widths in both themes with zero unsigned gaps
   PROOF: `node <the lane's engagement journey> --coverage`
11. Close-out — 0%
   DEFINITION OF DONE: every §3b row VERIFIED, the postmortem written, everything left on the machine declared with its size
   PROOF: `python3 projects/ops/agents/check_plan.py --progress projects/ops/life-os/REGROUP-2026-09-08/plans/LINKEDIN-ENGINE/PLAN.proposed.txt`

## NEXT

Everything found after the FINISH LINE passes goes here as one line, and is not worked. Empty at plan time.

## POSTMORTEM

Written by STEP 11 when the lane closes. Empty at plan time.

Current state PROGRESS.txt

LINKEDIN ENGAGEMENT LANE — PROGRESS. Dated lines, newest last. This file is the lane's STATE FILE (plan §5, walk-away contract).

2026-09-10 — Lane opened as Group D's second lane, beside JASMIN-CAPTUS, which is untouched. PLAN.proposed.txt written in the plan skill's 2026-09-09 shape: eleven steps, nine FRONT and two POLISH, every step carrying a FOR NICK line, four named models, one definition of done and one proof. Nothing is built. Every step reads 0%, no dispatch has run, and no VERIFIED line exists anywhere in the plan.

2026-09-10 — Measured at plan time, first-hand, so no step rests on an assumption: DeepAPI's LinkedIn people search answers on this account and accepted a zero-spend dry run (status dry_run, debitMicrousd 0); its live contract was read the same minute and names the body fields the plan uses. The encrypted vault holds 131 entries and none is a LinkedIn login. The scheduled-job runner holds zero Captus rows, so neither existing Captus job is on a clock and this lane's cadence is a handoff, never an assumption. DeepAPI's hosted browser endpoint refuses logins in its own safety block, which is why the posting arm is built on the paired-profile pattern that already works here rather than on that endpoint.

2026-09-10 — Correction carried into the plan rather than left in a note: the humanizer service the source spec named as the anti-slop pass has never run in this workspace. Its key authenticates but is refused for lack of the humanizer scope, and the one real nine-post comparison of it recommends against treating a pass as an improvement. The plan's anti-slop gate is therefore the AI-writing-tell checklist that does run here today, with the service named in the plan's decision list as an optional upgrade and not a dependency.