From an Upwork press release to an auditable product collection
A press release is not a case, a product is not a launch event, and a company claim is not automatically a metric. This tutorial follows the intake-first workflow that emerged when we modeled real Upwork product announcements by hand.
What we are testing
- Can material exist before cases?
- Can one source create several cases?
- Can products survive many launch stages?
- Can umbrella and atomic products coexist?
- Can metrics be applied after collection?
- Can absence retain its meaning?
- Can heterogeneous evidence fail safely?
- Does EDSL remain optional and bounded?
1The new model
The original design expected research to produce cases and observations directly. Real Upwork announcements made that unrealistic. A single release introduces several products; a named product appears in many later releases; the same language can describe a beta, rollout, integration, or expansion.
Collection
An exploratory corpus such as “Upwork product launches.” It can contain ambiguous material without yet claiming to be a valid reference class.
Reference class
An approved analytical use of a collection with a versioned unit, membership rule, metric, and analysis set.
This distinction lets us learn what the entities are before pretending we know how to compare their outcomes.
2Start an exploratory collection
mkdir upwork-launches
cd upwork-launches
flyvbjerg init
flyvbjerg collection new upwork-product-launches \
--title "Upwork customer-facing product launches"
Show representative response
{
"schema_version": "1.0",
"command": "flyvbjerg collection new upwork-product-launches",
"status": "ok",
"data": {
"collection_id": "upwork-product-launches",
"state": "exploratory",
"cases": 0,
"untriaged_items": 0
},
"artifacts": [{
"path": ".flyvbjerg/collections/upwork-product-launches/collection.json",
"role": "collection_record",
"media_type": "application/json",
"sha256": "sha256:..."
}],
"warnings": [],
"errors": [],
"next_steps": [{
"id": "register_source",
"purpose": "Register material found by the research agent",
"command": "flyvbjerg source add upwork-product-launches --file PATH --url URL",
"mutates_state": true,
"requires_network": false,
"requires_user_approval": false
}]
}
No metric or inclusion rule is required yet. We know only that we want to explore named Upwork customer-facing products and launch activity.
3The agent performs the research
The coding agent searches Upwork’s press-release index and investor materials with its own browser and web tools. Flyvbjerg neither runs these searches nor downloads their results.
| Agent action | Flyvbjerg action |
|---|---|
| Search Upwork investor relations | None |
| Open and assess a press release | None |
| Save an official PDF or permitted HTML capture | Register and hash the supplied file |
| Notice several product announcements | Store bounded intake items |
| Decide two names refer to one product | Record an explicit resolution |
The first pass uses Upwork’s July 2023 AI launch, November 2023 Chat Pro release, Spring 2024 Updates, Fall 2024 Updates, and quarterly results.
4Register a source and its capture
A source is bibliographic identity; a capture is preserved content. They are separate because one webpage may have an official PDF, extracted Markdown, and later revisions.
flyvbjerg source add upwork-product-launches \
--file collected/upwork-spring-2024.pdf \
--url https://investors.upwork.com/news-releases/news-release-details/upwork-introduces-upwork-updates-launch-ai-innovations-and-other \
--title "Upwork Updates: Spring 2024" \
--kind press_release
Show the resulting identities
{
"status": "ok",
"data": {
"source": {
"source_id": "src_spring_2024",
"publisher": "Upwork Inc.",
"published": "2024-04-30",
"url": "https://investors.upwork.com/..."
},
"capture": {
"capture_id": "cap_spring_2024_pdf",
"original_name": "upwork-spring-2024.pdf",
"media_type": "application/pdf",
"sha256": "sha256:91a...",
"method": "agent_supplied"
}
},
"artifacts": [{
"path": ".flyvbjerg/collections/upwork-product-launches/intake/captures/cap_spring_2024_pdf/upwork-spring-2024.pdf",
"role": "source_capture",
"media_type": "application/pdf",
"sha256": "sha256:91a..."
}]
}
5Create intake items without inventing cases
The Spring 2024 release mentions Uma, Best Match insights, Job Post Generator, Proposal Tips, Chat Pro, Boosted Profiles, Portfolio, Coaching Services, and other offerings. The agent records useful bounded excerpts before deciding what each name represents.
flyvbjerg intake add upwork-product-launches \
--source src_spring_2024 \
--capture cap_spring_2024_pdf \
--kind candidate_entity \
--text "The Spring 2024 Update is headlined by Uma, Upwork's Mindful AI." \
--locator '{"page":1,"heading":"Introducing Uma"}' \
--proposed-name Uma
flyvbjerg intake add upwork-product-launches \
--source src_spring_2024 \
--capture cap_spring_2024_pdf \
--kind candidate_event \
--text "Portfolio, now available in beta..." \
--locator '{"page":3,"heading":"New Resources for Freelancers"}' \
--proposed-name Portfolio
Both items are untriaged. Neither creates a product, a launch, or a metric value. The intake record says only: this bounded information may matter, and here is exactly where it came from.
flyvbjerg intake list upwork-product-launches --status untriaged
flyvbjerg intake next upwork-product-launches
6Resolve persistent product cases
After reading the July 2023 and Spring 2024 sources together, the agent concludes that Job Post Generator is a persistent product feature discussed in several releases. It resolves the item into a new case.
flyvbjerg intake resolve-case upwork-product-launches item_job_generator \
--new-case job-post-generator \
--name "Job Post Generator"
flyvbjerg intake resolve-case upwork-product-launches item_job_generator_2024 \
--case job-post-generator
The first command creates the identity; the second links later material to it. Both immutable resolutions remain. The same pattern creates candidates for Proposal Tips, Upwork Chat Pro, Uma, and Boosted Profiles.
This many-to-many structure is why sources stay at collection level rather than being copied into every dossier.
7Resolve launch stages as events
Upwork Chat Pro was premiered with a waitlist, later bundled into Freelancer Plus, and later reported 68% quarterly daily-active-user growth. Those are three events involving one persistent case.
flyvbjerg intake resolve-event upwork-product-launches item_chat_pro_premiere \
--type beta_announced \
--case upwork-chat-pro \
--date 2023-11-06 \
--date-precision day
flyvbjerg event add upwork-product-launches \
--type bundled \
--case upwork-chat-pro \
--date 2024-05-01 \
--source src_2024_q1_results
flyvbjerg event add upwork-product-launches \
--type outcome_disclosed \
--case upwork-chat-pro \
--date 2024-08-07 \
--source src_2024_q2_results
| Date | Event | What it does not mean |
|---|---|---|
| 2023-11-06 | Beta announced; waitlist opened | General availability |
| 2024-05-01 | Bundled into Freelancer Plus | A new product identity |
| 2024-08-07 | DAU growth disclosed | Causal proof of product success |
A multi-product Upwork Updates release can also be one event linked to many cases. Events live at collection level and dossiers compose references to them.
8Relate umbrella products to features
Uma is described both as an AI technology underlying existing experiences and as a conversational companion. It should not be flattened into the same entity level as every feature it powers.
flyvbjerg relationship add upwork-product-launches \
--from job-post-generator \
--type powered_by \
--to uma \
--source src_spring_2024
flyvbjerg relationship add upwork-product-launches \
--from upwork-chat-pro \
--type powered_by \
--to uma \
--source src_spring_2024
The inverse powers edges are derived. Relationships do not copy observations: Chat Pro’s DAU growth is not Uma-wide adoption, and portfolio revenue is not Boosted Profiles revenue.
9Inspect a rich case dossier
flyvbjerg case show upwork-product-launches upwork-chat-pro
Hide representative composed dossier
{
"case": {
"case_id": "upwork-chat-pro",
"name": "Upwork Chat Pro",
"entity_type": "integrated_application",
"membership": "candidate"
},
"sources": [
"src_2023_11_chat_pro",
"src_spring_2024",
"src_2024_q1_results",
"src_2024_q2_results"
],
"events": [
{"type":"beta_announced","date":"2023-11-06"},
{"type":"bundled","date":"2024-05-01"},
{"type":"outcome_disclosed","date":"2024-08-07"}
],
"relationships": [
{"type":"powered_by","to":"uma"},
{"type":"bundled_into","to":"freelancer-plus"}
],
"observations": [],
"coverage": {},
"open_questions": [
"Does a waitlist premiere qualify under the eventual class definition?"
]
}
The dossier is composed from shared records; it does not duplicate source or event bodies. Its optional README.md can contain richer agent notes but is not structured evidence by itself.
10Add metrics only after seeing what is comparable
The first pass finds heterogeneous disclosures:
- Job Post Generator: approximately 70–73% faster job-post completion.
- Proposal Tips: qualitative faster work acquisition.
- Chat Pro: daily active users grew 68% quarter over quarter.
- Boosted Profiles: feature expansion and only portfolio-level revenue.
- Uma: capability expansion without a clean product-wide adoption figure.
We can still define a narrow metric that applies where Upwork actually disclosed it:
flyvbjerg metric add upwork-product-launches daily-active-user-growth-qoq \
--kind numeric \
--role outcome \
--unit percent \
--description "Quarter-over-quarter change in product daily active users"
flyvbjerg observation add upwork-product-launches upwork-chat-pro \
--metric daily-active-user-growth-qoq \
--value 68 \
--period 2024Q2 \
--source src_2024_q2_results \
--locator '{"heading":"Artificial Intelligence"}' \
--method reported
The observation is a candidate. A separate decision accepts it after checking the source and definition:
flyvbjerg observation decide upwork-product-launches obs_chat_pro_dau_q2 \
--accept \
--reason "The primary source reports the same construct, period, and unit."
11Say why a value is absent
The new metric should not produce four generic nulls. Each product has a different evidence state.
flyvbjerg coverage set upwork-product-launches job-post-generator \
--metric daily-active-user-growth-qoq \
--state not_disclosed \
--reason "Reviewed FY2023 and Q3 2024 disclosures report workflow speed, not DAU growth." \
--source src_2024_q3_results
flyvbjerg coverage set upwork-product-launches boosted-profiles \
--metric daily-active-user-growth-qoq \
--state not_researched \
--reason "No bounded review for a product-level DAU disclosure has been completed."
flyvbjerg coverage set upwork-product-launches uma \
--metric daily-active-user-growth-qoq \
--state not_applicable \
--reason "Uma is currently modeled as an umbrella platform, not one atomic application."
| State | Meaning |
|---|---|
not_researched | The agent has not completed a bounded search. |
not_disclosed | Relevant reviewed sources do not disclose the value. |
not_found_in_reviewed_sources | A named capture set was checked without locating it. |
not_applicable | The construct does not apply to this entity. |
censored | The relevant outcome window has not closed. |
conflicted | Supported claims disagree. |
12Freeze an analysis—and let it fail honestly
flyvbjerg analysis create upwork-product-launches \
--name launch-adoption-first-pass \
--metric daily-active-user-growth-qoq \
--cutoff 2026-08-11
Show representative warning response
{
"status": "ok",
"data": {
"analysis_id": "analysis_launch_adoption_v1",
"eligible_cases": 4,
"observed_cases": 1,
"coverage": {
"observed": 1,
"not_disclosed": 1,
"not_researched": 1,
"not_applicable": 1
},
"reference_distribution_ready": false
},
"warnings": [
{"code":"insufficient_observations","message":"One observation cannot support a reference distribution."},
{"code":"mixed_entity_levels","message":"The collection contains umbrella and atomic product entities."}
],
"next_steps": [{
"id": "inspect_gaps",
"purpose": "Review case-specific coverage before attempting a rate",
"command": "flyvbjerg gaps upwork-product-launches --metric daily-active-user-growth-qoq",
"mutates_state": false,
"requires_network": false,
"requires_user_approval": false
}]
}
This is a successful analytical result: the material does not support the proposed comparison. The tool preserves a useful product corpus without manufacturing a base rate from incompatible disclosures.
13Use EDSL only for bounded processing
If the collection grows to dozens of earnings transcripts, the agent may ask Flyvbjerg to package extraction over captures already present in the workspace.
flyvbjerg process plan upwork-product-launches \
--name transcript-product-mentions-v1 \
--mode extract \
--capture cap_q1_2024_transcript \
--capture cap_q2_2024_transcript
flyvbjerg process approve run_transcript_mentions_v1
flyvbjerg process build run_transcript_mentions_v1 \
--output .flyvbjerg/collections/upwork-product-launches/runs/run_transcript_mentions_v1
The package contains native EDSL Jobs and Models. Flyvbjerg returns—but never runs—the external commands:
ep inspect .flyvbjerg/collections/upwork-product-launches/runs/run_transcript_mentions_v1/jobs.ep
ep jobs cost .flyvbjerg/collections/upwork-product-launches/runs/run_transcript_mentions_v1/jobs.ep
ep run .flyvbjerg/collections/upwork-product-launches/runs/run_transcript_mentions_v1/jobs.ep \
--output transcript-product-mentions.results.ep
14What remains on disk
Run flyvbjerg describe PATH to trace a record or artifact back through its source, capture, intake item, resolution, and decision. Run flyvbjerg next after every material stage so repository state—not chat memory—determines the next action.
15What this version gets right
| Observed problem | Design response |
|---|---|
| A release mentions many products. | Register once; create several intake items and resolutions. |
| A product appears in many releases. | One persistent case with many source links and events. |
| Beta, rollout, bundling, and expansion differ. | First-class lifecycle events rather than one overloaded launch date. |
| Uma spans atomic features. | Typed entity levels and relationships. |
| Outcomes use incompatible constructs. | Metric definitions come later; no generic success score. |
| Missing values have different causes. | Explicit coverage states rather than null. |
| The corpus may never support a forecast. | A collection remains valuable without being called a reference class. |
flyvbjerg validate --collection upwork-product-launches
flyvbjerg next upwork-product-launches