Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: Tests

on:
pull_request:
push:
branches: [main]

permissions:
contents: read

jobs:
unittest:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- run: python -m unittest discover -s tests -v
26 changes: 22 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ Requires a Pro, Max, Team, or Enterprise plan with code execution enabled (Setti

1. Open **Customize → Plugins** and click **+ → Add marketplace**.
2. Sync from this GitHub repo: `noticedso/skills`.
3. From the synced marketplace, install **Networking Assistant**. All seven skills land in one shot.
3. From the synced marketplace, install **Networking Assistant**. All skills land in one shot.

## install (ChatGPT / Codex)

Add `noticedso/skills` as a plugin marketplace, then install **Networking Assistant**. The plugin keeps all seven capabilities together; do not upload them one by one.
Add `noticedso/skills` as a plugin marketplace, then install **Networking Assistant**. The plugin keeps the capabilities together; do not upload them one by one.

## the skills

Expand All @@ -38,7 +38,9 @@ Add `noticedso/skills` as a plugin marketplace, then install **Networking Assist
| **search-network** | Natural-language search across your network; returns a tight table; drills into anyone. | read-only |
| **match-identities** | Triage the cross-source identity matches noticed proposes — confirm real matches, clear false positives, merge duplicates — and fill profile gaps by searching your network first. | yes |
| **intro** | Find the warm path to a target, draft an intro between two people, write an intro request with a forwardable blurb, or write a backchannel vouch — all copy/paste. | only on confirmed intro tracking |
| **collect-company-context** | Gather onboarding context, propose goal-aligned target profiles and fill the Notion Company context template. | company context and Library updates |
| **collect-company-context** | Gather and verify the seven-section Company Brain while preserving sources in its Library. | Company Brain and Library updates |
| **daily-opportunities** | Prepare 25-person lists from observable segment rules, learn from Yes/No/Not sure reviews, and refine the next batch. | authorized lists and dashboard updates |
| **daily-outreach** | Develop attributed outreach for an approved person and reconcile confirmed sends, replies and meetings. | authorized drafts and dashboard updates; never sends automatically |

## how they fit together

Expand All @@ -51,6 +53,17 @@ add-person → event-debrief

`research-person` and `follow-up` support any contact at any time. `search-network` is the read path over everything you've captured. `intro` turns that network into warm paths and the messages that open them.

The research-partner loop is:

```
Company Brain → Goal → Segment rules → List → Review → Learning → Outreach
```

`collect-company-context` maintains the Company Brain. `daily-opportunities`
creates and refines reviewed lists. `daily-outreach` turns explicit Yeses into
attributed drafts and records confirmed outcomes. The routines share one workflow
contract so the three skills use the same schema and definitions.

## shared conventions

All skills follow the same rules, so they read as one system:
Expand Down Expand Up @@ -90,4 +103,9 @@ reinstalling the plugin.

## Manual opportunity and outreach routines

`daily-opportunities` prepares or refines a fixed 25-person review list for one selected target profile. `daily-outreach` turns current human Yeses into Notion drafts and reconciles confirmed sends, replies and bookings. Run one requested step through Codex with noticed MCP and Notion. These skills do not schedule work or send messages. Customer context and iteration history remain in the private customer workspace.
`daily-opportunities` prepares or refines a 25-person review list for one
selected segment. `daily-outreach` turns current human Yeses into attributed
Notion drafts and reconciles confirmed sends, replies and bookings. Run one
requested step through Codex with noticed MCP and Notion. These skills do not
schedule work or send messages. Company facts, rules, lists and results remain in
the partner's private workspace.
54 changes: 47 additions & 7 deletions TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,18 +63,58 @@ Use synthetic company context and fixture-only tools; inspect proposed operation
| Scenario | Expected behavior |
|---|---|
| Onboard a new research partner from a supplied directory with its own default template. Repeat with a matching partner already present. | Create the canonical record from the discovered template only when absent; otherwise reuse it. Save context there, never on a standalone staging page. |
| Save research and proposed profiles before the user validates the reference. | Saved content remains visibly draft/unvalidated; Last reviewed changes only after explicit reference validation. Profile selection alone is insufficient. |
| Request strategic investor/industry connectors in a city while the connector definition is absent, empty, a placeholder or substantive but unagreed. | Return to company-context/profile definition using the request as input; do not create an iteration, hypothesis or candidate list yet. |
| Run opportunities for a saved, explicitly agreed investor profile while another track is undefined and unrelated company context remains draft. | Proceed within the agreed profile and engagement goal without demanding full reference validation or treating investors as sales prospects. |
| Choose investors and partners for the direct engagement tracks of a company whose customers are hotels. Rename the context sections. | Preserve hotels as the stable customer ICP separately from both engagement tracks; follow sections by meaning without forcing customers into ICP #1. |
| Refine a hypothesis after a reviewed batch within an agreed track. | Use recorded learning within that track; preserve the existing batch, review-readiness and recovery safeguards. |
| Draft outreach for approved relationships in customer, investor, partner and hiring tracks, with each goal and track saved in context. | Inherit the relevant goal and track rather than treating every recipient as a buyer. Use the offer where relevant, preserve human approval and drafts, and leave sending to the user. |
| Save company research with two facts still needing confirmation. | Use the exact seven Company Brain headings, keep sources in Library, and mark only the unconfirmed facts red. Do not add target-profile prose to the Brain. |
| The company has buyer and investor goals with different names across old content. | Reuse one dashboard, choose one exact name for each goal, and use it consistently in Progress, Segments and Lists. Never combine their metrics. |
| Prepare a first list for a broad investor segment with Required, Prefer and Exclude rules. | Evaluate only public/enrichment signals, create one 25-person batch, configure the exact “This looks like a good fit…” review statement, and preserve the rules and evidence. |
| A list has 10 Yes, 12 No and 3 Not sure reviews with useful reasons. | Record the result, add one learning-log row, make the smallest supported rule refinement, and preserve the historic list and verdicts. |
| A list has only 10/25 reviews, including three Yeses. | Keep the list open for 15 reviews and allow those three approved people to move to Outreach without replacing the batch. |
| Create outreach for an approved person found in two lists for the same goal. | Reuse one Outreach entry, set the list that motivated the message as Source list, and keep other evidence in History & source. |
| A draft exists but no send is confirmed; later the user confirms a LinkedIn message was sent. | Keep To contact and Sent on empty before confirmation; then record Contacted, the channel and actual send date. Do not add follow-up-management fields. |
| A connector replies and offers an introduction, but the target has not replied and no meeting is accepted. | Preserve the evidence without counting a target reply or booked meeting. Attribute the activity through Source list → Goal. |

### Run the research-partner evals

Contract checks run in CI and need no model:

```bash
python -m unittest discover -s tests -v
```

Behavioral decision evals use synthetic fixtures and the checked-out skill text.
They require an authenticated Codex CLI but do not connect to noticed, Notion or
the web:

```bash
python scripts/run_research_partner_evals.py --repeats 3
```

The runner scores state transitions and forbidden behavior deterministically.
Run it several times because model behavior is nondeterministic. Before releasing
a material workflow change, also perform one smoke test with a duplicated Notion
dashboard and disposable noticed team; fixture evals do not prove live tool
integration.

### Live integration smoke

On 19 Sep 2026, the research-partner flow was exercised with disposable artifacts:

- [Notion dashboard](https://app.notion.com/p/3e0efbd6e46b818dbe6bcc9af74cafd8)
- noticed team: `Research partner skill eval — 2026-09-19`
- [noticed list](https://www.noticed.so/goals/850d8452-899a-47f8-8cfd-0014674fb64c)

The default template created the Company Brain and embedded Segments, Lists, and
Outreach databases. The smoke test saved confirmed and unconfirmed company facts,
observable segment rules, a team-owned list with the canonical review question,
and an unsent outreach entry attributed through Source list. Every write was read
back. No message was sent, no meeting was recorded, and no customer workspace was
changed.

## what to watch for

The behaviors the NYTW revisions were built to get right — confirm they hold across the tests above:

- **Capture skills preview before saving.** Company-context drafts may be saved incrementally; saving is not validation.
- **Company facts stay separate from targeting.** The Brain uses the seven canonical headings; observable rules and learning stay in Segments and Lists.
- **Outreach attribution is complete.** Every Outreach entry has a Source list, which resolves its Segment and Goal.
- **No bare-name guessing.** A name with no URL/handle triggers a question, not a silent new record.
- **The readback always appears** after a save, and reads like a person talking, not a field dump.
- **Provenance tags never leak into chat** — you should never see `[from user]` or `[research, unverified]` in a message, only in the stored note.
Expand Down
127 changes: 127 additions & 0 deletions evals/research_partner/cases.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
[
{
"id": "company_context_updates_canonical_brain",
"skill": "skills/collect-company-context/SKILL.md",
"request": "Update the company context from these new notes.",
"fixture": {
"dashboard": {"id": "company-1", "exists": true},
"facts": [
{"id": "current-price", "text": "Implementation currently costs €3,000.", "confirmed": true},
{"id": "planned-subscription", "text": "A subscription may be introduced later.", "confirmed": false}
],
"sources": ["call-2026-09-18", "pricing-page"],
"targeting_idea": "Prefer companies with active hiring."
},
"assertions": [
{"path": "action", "op": "eq", "value": "update_company_brain"},
{"path": "creates_new_dashboard", "op": "eq", "value": false},
{"path": "company_brain_sections", "op": "contains_all", "value": ["Company & product", "Pricing & business model", "Market & positioning", "Customer needs & examples", "Business goals & baseline", "Sales process & channels", "Team & responsibilities"]},
{"path": "unconfirmed_facts", "op": "contains_all", "value": ["planned-subscription"]},
{"path": "writes_targeting_rules_to_brain", "op": "eq", "value": false},
{"path": "preserves_history", "op": "eq", "value": true}
]
},
{
"id": "partial_review_resumes_existing_batch",
"skill": "skills/daily-opportunities/SKILL.md",
"request": "Run daily opportunities for the Partners goal and the founder-led sales segment.",
"fixture": {
"goal": "Partners",
"segment": "Founder-led B2B software sales",
"list": {"id": "list-partners-1", "review_state": "Awaiting review", "member_count": 25},
"reviews": {
"yes": ["p1", "p2", "p3"],
"no": ["p4", "p5", "p6", "p7", "p8"],
"not_sure": ["p9", "p10"]
}
},
"assertions": [
{"path": "action", "op": "eq", "value": "wait_for_reviews"},
{"path": "creates_new_list", "op": "eq", "value": false},
{"path": "list_id", "op": "eq", "value": "list-partners-1"},
{"path": "review_state", "op": "eq", "value": "Awaiting review"},
{"path": "reviewed_count", "op": "eq", "value": 10},
{"path": "remaining_reviews", "op": "eq", "value": 15},
{"path": "approved_for_outreach", "op": "contains_all", "value": ["p1", "p2", "p3"]},
{"path": "rule_changes", "op": "len_eq", "value": 0},
{"path": "preserves_history", "op": "eq", "value": true}
]
},
{
"id": "completed_review_records_one_refinement",
"skill": "skills/daily-opportunities/SKILL.md",
"request": "Process the completed review for this Partners list.",
"fixture": {
"goal": "Partners",
"segment": "Founder-led B2B software sales",
"rules": {"required": ["B2B software", "Founder or CEO"], "prefer": ["15-50 employees"], "exclude": []},
"list": {"id": "list-partners-2", "review_state": "Awaiting review", "member_count": 25},
"reviews": {
"pages": [
{"yes_count": 7, "no_count": 3, "not_sure_count": 0, "has_more": true},
{"yes_count": 3, "no_count": 9, "not_sure_count": 3, "has_more": false}
],
"repeated_reason": "Companies under 10 employees were consistently rejected as too early."
}
},
"assertions": [
{"path": "action", "op": "eq", "value": "record_learning"},
{"path": "creates_new_list", "op": "eq", "value": false},
{"path": "list_id", "op": "eq", "value": "list-partners-2"},
{"path": "review_state", "op": "eq", "value": "Reviewed"},
{"path": "reviewed_count", "op": "eq", "value": 25},
{"path": "remaining_reviews", "op": "eq", "value": 0},
{"path": "rule_changes", "op": "len_lte", "value": 1},
{"path": "metric_goal", "op": "eq", "value": "Partners"},
{"path": "preserves_history", "op": "eq", "value": true}
]
},
{
"id": "outreach_uses_motivating_source_list",
"skill": "skills/daily-outreach/SKILL.md",
"request": "Prepare outreach for Ana. Her review verdict is Yes.",
"fixture": {
"person": {"id": "ana", "name": "Ana Silva"},
"goal": "Partners",
"lists": [
{"id": "list-partners-1", "contains_person": true, "motivated_outreach": false},
{"id": "list-partners-2", "contains_person": true, "motivated_outreach": true}
],
"existing_outreach": null,
"send_confirmation": null
},
"assertions": [
{"path": "action", "op": "eq", "value": "create_outreach"},
{"path": "outreach_person_id", "op": "eq", "value": "ana"},
{"path": "source_list", "op": "eq", "value": "list-partners-2"},
{"path": "outreach_status", "op": "eq", "value": "To contact"},
{"path": "sent_on", "op": "is_null", "value": null},
{"path": "metric_goal", "op": "eq", "value": "Partners"},
{"path": "preserves_history", "op": "eq", "value": true}
]
},
{
"id": "connector_reply_does_not_count_as_target_reply",
"skill": "skills/daily-outreach/SKILL.md",
"request": "Record the latest outreach outcome.",
"fixture": {
"person": {"id": "target-1", "name": "Target Person"},
"goal": "Investors",
"source_list": "list-investors-1",
"existing_outreach": {"status": "Contacted", "sent_on": "2026-09-18"},
"events": [
{"actor": "connector-1", "type": "reply", "text": "Happy to introduce you."},
{"actor": "target-1", "type": "reply", "present": false},
{"actor": "target-1", "type": "accepted_meeting", "present": false}
]
},
"assertions": [
{"path": "action", "op": "eq", "value": "record_outcome"},
{"path": "source_list", "op": "eq", "value": "list-investors-1"},
{"path": "outreach_status", "op": "eq", "value": "Contacted"},
{"path": "sent_on", "op": "eq", "value": "2026-09-18"},
{"path": "metric_goal", "op": "eq", "value": "Investors"},
{"path": "preserves_history", "op": "eq", "value": true}
]
}
]
81 changes: 81 additions & 0 deletions evals/research_partner/output.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"additionalProperties": false,
"required": [
"case_id",
"action",
"creates_new_dashboard",
"creates_new_list",
"list_id",
"review_state",
"reviewed_count",
"remaining_reviews",
"approved_for_outreach",
"results_summary",
"rule_changes",
"outreach_person_id",
"source_list",
"outreach_status",
"sent_on",
"metric_goal",
"company_brain_sections",
"unconfirmed_facts",
"writes_targeting_rules_to_brain",
"preserves_history",
"notes"
],
"properties": {
"case_id": {"type": "string"},
"action": {
"type": "string",
"enum": [
"update_company_brain",
"create_list",
"wait_for_reviews",
"record_learning",
"create_outreach",
"update_outreach",
"record_outcome",
"blocked"
]
},
"creates_new_dashboard": {"type": "boolean"},
"creates_new_list": {"type": "boolean"},
"list_id": {"type": ["string", "null"]},
"review_state": {"type": ["string", "null"]},
"reviewed_count": {"type": ["integer", "null"]},
"remaining_reviews": {"type": ["integer", "null"]},
"approved_for_outreach": {
"description": "All fixture person IDs with an explicit Yes that remain eligible for outreach after this action, even when outreach is not created now.",
"type": "array",
"items": {"type": "string"}
},
"results_summary": {"type": ["string", "null"]},
"rule_changes": {"type": "array", "items": {"type": "string"}},
"outreach_person_id": {"type": ["string", "null"]},
"source_list": {"type": ["string", "null"]},
"outreach_status": {"type": ["string", "null"]},
"sent_on": {"type": ["string", "null"]},
"metric_goal": {
"description": "The exact goal whose metrics this decision affects or will update; null only when the action has no goal-level metric effect.",
"type": ["string", "null"]
},
"company_brain_sections": {
"description": "Every canonical Company Brain section present after this action, not only sections edited by the action.",
"type": "array",
"items": {"type": "string"}
},
"unconfirmed_facts": {
"description": "Fixture fact IDs that remain unconfirmed after this action; use IDs rather than fact text.",
"type": "array",
"items": {"type": "string"}
},
"writes_targeting_rules_to_brain": {"type": "boolean"},
"preserves_history": {
"description": "True when existing lists, reviews, human edits, activity, and relevant evidence from other lists are preserved.",
"type": "boolean"
},
"notes": {"type": "array", "items": {"type": "string"}}
}
}
Loading
Loading