Expected Parrot · Real-company design simulation

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.

An Expected Parrot assembling a Danish historical reference class inside EDSL brackets
Worked design tutorial. Flyvbjerg 0.1 now implements the core intake, case, metric, analysis, and bounded EDSL workflow. Some responses below remain illustrative because they expose the intended complete interface, including later convenience commands. The source facts come from real Upwork public materials. See the specification, practice notes, and hand-built simulation.

What we are testing

  1. Can material exist before cases?
  2. Can one source create several cases?
  3. Can products survive many launch stages?
  4. Can umbrella and atomic products coexist?
  5. Can metrics be applied after collection?
  6. Can absence retain its meaning?
  7. Can heterogeneous evidence fail safely?
  8. 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.

CollectRegister sources and preserved captures once.
ExtractCreate small, locatable intake items without forcing identity.
ResolveForm persistent cases, events, and relationships.
MeasureDefine metrics only when cases support comparison.
AnalyzeFreeze accepted evidence and expose every gap.

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 actionFlyvbjerg action
Search Upwork investor relationsNone
Open and assess a press releaseNone
Save an official PDF or permitted HTML captureRegister and hash the supplied file
Notice several product announcementsStore bounded intake items
Decide two names refer to one productRecord 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..."
  }]
}
A URL is not a capture. If the agent registers only a URL, Flyvbjerg records it but warns that no durable local content exists. It never follows the URL itself.

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.

one source ───────────────┬─→ Uma ├─→ Job Post Generator Spring 2024 press release ────┼─→ Proposal Tips ├─→ Upwork Chat Pro ├─→ Boosted Profiles └─→ Portfolio one product ←── July 2023 release ←── Spring 2024 release ←── Q3 2024 financial results

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
DateEventWhat it does not mean
2023-11-06Beta announced; waitlist openedGeneral availability
2024-05-01Bundled into Freelancer PlusA new product identity
2024-08-07DAU growth disclosedCausal 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
Uma [umbrella AI platform / companion] ├── powers → Job Post Generator ├── powers → Proposal Tips ├── powers → Upwork Chat Pro └── powers → Best Match insights

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.
There is no honest `product_success` metric here. Defining one would hide different constructs, denominators, and disclosure choices behind one label. The collection is useful even though it is not yet a reference class.

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."
StateMeaning
not_researchedThe agent has not completed a bounded search.
not_disclosedRelevant reviewed sources do not disclose the value.
not_found_in_reviewed_sourcesA named capture set was checked without locating it.
not_applicableThe construct does not apply to this entity.
censoredThe relevant outcome window has not closed.
conflictedSupported 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
Explicit execution boundary. The user must inspect the Jobs and authorize model execution. Results are audited against the original Jobs and captures, then ingested as intake items or candidate observations—not accepted facts.

14What remains on disk

.flyvbjerg/collections/upwork-product-launches/ ├── collection.json ├── definition.md ├── intake/ │ ├── sources/ shared bibliographic records │ ├── captures/ immutable preserved files │ ├── items/ untriaged and resolved excerpts │ └── resolutions/ how items became domain records ├── cases/ │ ├── job-post-generator/ │ ├── proposal-tips/ │ ├── upwork-chat-pro/ │ ├── uma/ │ └── boosted-profiles/ ├── events/ shared and case-specific lifecycle events ├── metrics/ definitions added after case construction ├── analysis-sets/ frozen evidence selections └── runs/ optional EDSL Jobs, Results, audits, retries

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 problemDesign 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.
The first implementation target is now concrete. It should reproduce this intake, triage, case composition, relationship, event, metric, coverage, and failed-analysis flow without model calls. EDSL processing is the second slice; forecasting comes only after a simulation yields genuinely comparable outcomes.
flyvbjerg validate --collection upwork-product-launches
flyvbjerg next upwork-product-launches