---
title: "Subjects"
url: https://services.dev/docs/service/subject
captured: [legal-walk.txt, service-subject.txt, subject-tabs.txt, subject-watch.txt, tax-cases.txt, tax-check.txt]
codes: [not_found]
---
# Subjects

URL: https://services.dev/docs/service/subject

> A subject is what outlives the job, keyed by an input or a receipt. It keeps typed facts across jobs, insert-only, tells every job that read a fact when an amendment restates it, holds one job of a Service per period, names who is of record, and keeps its open clocks in someone's hands between jobs and after a client leaves.

`subject` answers one question: what does this work belong to, that the next job should remember?

## Smallest form

One line names the subject, the period each job holds, and the fact it keeps:

```ts title="services/glossary.ts"
  subject: { kind: 'account', key: 'account', period: 'year', keeps: { glossary: 'glossary' } },
```

The second year reads what the first one kept, with no input to carry it:

```txt
→ POST /glossary {"account":"acme","year":"2026","terms":["Premises"],"max_usd":19,"key":"glossary-2026"}
```

```txt title="captured/service-subject.txt · 202 · job_7Hq3 · delivered · $19.00 charged · magic values: none"
  output  {"glossary":["Lessee","Lessor","Premises"],"last_year":["Lessee","Lessor"]}
```

## Every form

**On the Service**

| Key | Written | Left out | Does |
|---|---|---|---|
| `subject` | `SubjectSpec` | none | What outlives the job: a subject keyed by an input or a receipt, the member of record on it, its kept facts, its periods, and the Service that watches its clocks. |

**`subject`, written out**

| Key | Written | Left out | Does |
|---|---|---|---|
| `kind` | `string` | required | What it is, in your words: 'patent family', 'trademark', 'company'. |
| `key` | `(keyof I \| keyof O) & string` | required | The input, or the output a receipt fills, whose value names it: `application_number`, `ein`. |
| `of_record` | `{ role: RoleWord; named_at: <step>; custodian?: (RoleWord \| 'brand')[] }` | none | The member of record: whoever attests at `named_at` stays of record on the subject across jobs. Withdrawal names a successor; a member whose credential or coverage lapses leaves it vacant, and `custodian` covers it, rung by rung, then the brand. |
| `keeps` | `K` | none | Facts the subject keeps across jobs, each from the output that writes it: `{ carryforwards: 'carryforwards', tax: 'tax' }`, typed as that output is. Written only when a job ends accepted, never when it is canceled or lapses, and keyed by the period the job held. Insert-only: each value records `{ value, job, step, at, period }`, and nothing is overwritten. |
| `period` | `(keyof I & string) \| ((job) => string) \| { of; ends?; many? }` | none | One live or ended job of this Service per subject per period: the input that names the period, or a function of the order. A second order for a period answers 409 `period_taken`, its next the follow-on the end offers, such as an amendment. A canceled job frees its period. `ends` gives the period's last day as `MM-DD`, for a fiscal year; `many: true` holds any number of jobs per period, as amendments do. |
| `steward` | `AnyService` | none | The Service that keeps the subject's clocks once a job ends with one still running: ordered with the subject's key, it keeps the date in someone's hands. Until it is, the customer portal offers it beside an ask in which the client acknowledges they own the date, and nothing lapses into a gap between jobs. |

**In your code, on the job**

| Key | Written | Left out | Does |
|---|---|---|---|
| `subject` | `SubjectView` | required | The job's subject, as it stands: its kept facts, and the same Service's work on it last period. |

## Kept facts

`keeps` maps a fact to the output that writes it. A fact is written only when a job ends accepted, keyed by the period that job held, and never overwritten.

Your code reads it on the job:

| In your code | Reads |
|---|---|
| `job.subject.fact('glossary')` | On a job with a period, the latest value from the closest period before its own; on one without, the latest written. Undefined until an accepted job writes it. |
| `job.subject.fact('glossary', { period: '2025' })` | The latest value written for that period. `'prior'` and `'latest'` name the two defaults. |
| `job.subject.fact('pages', { period: '2027', all: true })` | Every value of that period and the periods inside it, `2027-Q1` to `2027-Q4`, oldest first. |
| `job.subject.fact('glossary', { all: true })` | Every value, oldest first. |
| `job.subject.prior('draft')` | That step's committed output on the latest ended job of this Service with a lower period. |
| `ViewOf<typeof glossary>` | The job, typed from the definition, so `fact('glossary')` is a `string[]`. Code shared across Services takes `AnyView` and names the type: `fact<string[]>('glossary')`. |

So a late job for an earlier period never changes what the next one reads.

## When a fact is restated

The record keeps every fact a job read, with the value's id. A newer value written to a period another job read raises `fact.restated` on that reader and the subject:

| Then | Who |
|---|---|
| the reader's end offers its follow-on, in the customer portal and the owner console | by default |
| the plan mints the follow-on | a plan with `on.event: { 'fact.restated': amendedReturn }` |

apis.tax amends 2025 in November 2027, and the 2026 return read its carryforwards. The Tax Plan routes the event, and amendments sit outside its allowances, so the mint is offered at its price:

```bash
npx services.dev events --since 2027-11-30 --on sbj_7Hq2 --test-clock tc_7Hq4
```

```txt title="captured/tax-cases.txt · magic values: owner.email = signs@example.test, metadata.sandbox.outside = {"reject":{"file.US-NY":["ein_mismatch"]}}"
evt_9Ts1  job.accepted         job_4Ty3  amended-return · period 2025 · the subject keeps carryforwards 2025 and tax 2025, newer values
evt_9Ts2  fact.restated        sbj_7Hq2  carryforwards 2025 · read by job_4Tx3 (period 2026, at compute 2027-07-13)
evt_9Ts3  subscription.minted  sbn_7Hq2  fact.restated → amended-return · period 2026 · past the allowance: offered at $900.00
```

```bash
npx services.dev jobs get job_4Tx3 --test-clock tc_7Hq4
```

```txt title="captured/tax-cases.txt · magic values: owner.email = signs@example.test, metadata.sandbox.outside = {"reject":{"file.US-NY":["ein_mismatch"]}}"
job_4Tx3 · accepted · annual-return · period 2026 · $0.00 on sbn_7Hq2
  read     carryforwards 2025 · fct_7Hq4 (job_4Tx2) · restated by fct_7Hr9 (job_4Ty3) 2027-11-30
  offered  amended-return · period 2026 · $900.00 · next POST /jobs { "service": "amended-return", "from": "job_4Tx3" }
```

`GET /subjects/:id` is the whole record: every job that touched it, its history, each fact with every value, and the periods its jobs hold:

```txt
→ GET /subjects/sbj_7Hq2
```

```json title="captured/service-subject.txt · 200 · magic values: none"
{
  "id": "sbj_7Hq2",
  "object": "subject",
  "livemode": false,
  "kind": "account",
  "key": {
    "field": "account",
    "value": "acme"
  },
  "created": "2026-10-25T14:02:11.000Z",
  "jobs": [
    "job_7Hq2",
    "job_7Hq3"
  ],
  "of_record": null,
  "history": [
    {
      "at": "2026-10-25T14:02:11.000Z",
      "type": "opened",
      "job": "job_7Hq2"
    },
    {
      "at": "2026-10-25T14:02:11.000Z",
      "type": "joined",
      "job": "job_7Hq3"
    }
  ],
  "facts": {
    "glossary": {
      "latest": {
        "value": [
          "Lessee",
          "Lessor",
          "Premises"
        ],
        "job": "job_7Hq3",
        "step": "draft",
        "at": "2026-10-25T14:02:11.000Z",
        "period": "2026"
      },
      "periods": {
        "2025": {
          "value": [
            "Lessee",
            "Lessor"
          ],
          "job": "job_7Hq2",
          "step": "draft",
          "at": "2026-10-25T14:02:11.000Z",
          "period": "2025"
        },
        "2026": {
          "value": [
            "Lessee",
            "Lessor",
            "Premises"
          ],
          "job": "job_7Hq3",
          "step": "draft",
          "at": "2026-10-25T14:02:11.000Z",
          "period": "2026"
        }
      },
      "history": [
        {
          "value": [
            "Lessee",
            "Lessor"
          ],
          "job": "job_7Hq2",
          "step": "draft",
          "at": "2026-10-25T14:02:11.000Z",
          "period": "2025"
        },
        {
          "value": [
            "Lessee",
            "Lessor",
            "Premises"
          ],
          "job": "job_7Hq3",
          "step": "draft",
          "at": "2026-10-25T14:02:11.000Z",
          "period": "2026"
        }
      ]
    }
  },
  "periods": [
    {
      "period": "2025",
      "service": "glossary",
      "job": "job_7Hq2",
      "status": "accepted"
    },
    {
      "period": "2026",
      "service": "glossary",
      "job": "job_7Hq3",
      "status": "accepted"
    }
  ],
  "simulated": true
}
```

## Its clocks outlive the job

A clock with `lives_on: 'subject'` keeps running after the job that opened it ends. An agent reads a subject through its clocks, on any surface:

**CLI**

```bash title="CLI"
npx services.dev clocks list subject=sbj_7Hq2 --json   # the subject's clocks, its job ended
```

```txt title="captured/subject-tabs.txt · magic values: none"
      "id": "clk_7Hq2",
      "name": "franchise_tax",
      "lives_on": "subject",
      "status": "open",
      "due_on": "2027-03-25",
      "every": "year",
        "type": "buyer",
```

**API**

```bash title="API"
curl http://localhost:8787/clocks?subject=sbj_7Hq2   # the subject's clocks, its job ended
```

```txt title="captured/subject-tabs.txt · 200 · magic values: none"
      "id": "clk_7Hq2",
      "name": "franchise_tax",
      "lives_on": "subject",
      "status": "open",
      "due_on": "2027-03-25",
      "every": "year",
        "type": "buyer",
```

**MCP**

```txt title="MCP"
→ tools/call list_clocks {"subject":"sbj_7Hq2"}   # the subject's clocks, its job ended
```

```txt title="captured/subject-tabs.txt · magic values: none"
      "id": "clk_7Hq2",
      "name": "franchise_tax",
      "lives_on": "subject",
      "status": "open",
      "due_on": "2027-03-25",
      "every": "year",
        "type": "buyer",
```

**SDK**

```ts title="SDK"
await lingua.clocks.list({"subject":"sbj_7Hq2"})   // the subject's clocks, its job ended
```

```txt title="captured/subject-tabs.txt · magic values: none"
      "id": "clk_7Hq2",
      "name": "franchise_tax",
      "lives_on": "subject",
      "status": "open",
      "due_on": "2027-03-25",
      "every": "year",
        "type": "buyer",
```

## Every open clock has an owner

Every subject with an open clock has someone who answers for it. When a job ends with a clock still running, the record says so, and the client is asked to own the date:

```txt
→ GET /owner/jobs/job_7Hq2
```

```txt title="captured/subject-watch.txt · magic values: none"
  clock.outlived · franchise_tax · 2027-03-25 · the client owns it, or orders Company Watch
  ask.opened · own_dates · sign · buyer
```

`steward` names the Service that keeps the subject's dates instead. The customer portal offers it beside the ask: mark the date done, hand it to the steward, or sign that it is theirs:

```txt
→ GET /portal
```

```txt title="captured/subject-watch.txt · magic values: none"
  franchise tax · due 25 Mar 2027 · you answer for it · notice 23 Feb
    [Mark it done]  clock.satisfy · POST /clocks/clk_7Hq2/satisfy
    [Keep it with Company Watch · $9]  service.order · POST /company-watch
    [I'll own this date]  ask.complete · POST /asks/ask_7Hq2
```

```txt
→ GET /clocks?job=job_7Hq2
```

```txt title="captured/subject-watch.txt · 200 · OK · 1 clock · magic values: none"
  clk_7Hq2 · franchise_tax · due 2027-03-25 · buyer · open · on the subject · every year · the client signed for it 2026-10-25
```

Once a job of the steward joins the subject, the ask is voided. Nothing lapses into a gap between jobs:

```txt
→ GET /owner/jobs/job_7Hq3
```

```txt title="captured/subject-watch.txt · magic values: none"
  ask.opened · ask_7Hq3
  ask.voided · ask_7Hq3 · Company Watch keeps the dates now: job_7Hq4.
```

A client who leaves gets the same hand-back: when a canceled plan's last cycle ends, each open date the brand or a member of record answered for is offered to the client. apis.tax's steward, Carryforward Watch, is $9 a year, so the 2046 carryforward keeps an owner:

```txt
→ GET /portal/subjects/sbj_7Hq2/letter
```

```txt title="captured/tax-cases.txt · 200 · the disengagement letter · Acme Robotics · 2029-01-20 · magic values: owner.email = signs@example.test, metadata.sandbox.outside = {"reject":{"file.US-NY":["ein_mismatch"]}}"
  Your Tax Plan ended on 20 January 2029. These dates are still running, and from today they are yours to keep:
    2029-04-17  refund claims on your 2025 return close, and so does the IRS's time to assess it
    2030-07-11  the same, for your 2026 return
    2031-04-18  the same, for your 2027 return
    2046-12-31  your earliest carryforward expires, $13,800 of US credit from 2026
  Katherine Johnson, EA, stays the signer of record on each return until its date. Keep them with Carryforward Watch for $9 a year, and we answer for them; or confirm each is yours.
```

## Who is of record

`of_record` names the member who attests at a step. They stay of record on the subject across jobs, and a clock with `owner: 'of_record'` is theirs. Withdrawing names a successor who holds the role's credential. When the member's credential or coverage lapses, the seat turns vacant and `custodian` covers it, rung by rung, then the brand. [Of record](https://services.dev/docs/people/of-record.md) walks it.

## Periods

With `period`, a subject holds one job of the Service per period, and a second order for one is refused with its follow-on: [One job per period](https://services.dev/docs/service/periods.md).

## What happens on its own

A subject keeps no timer of its own. Its jobs, its clocks and the people on it move it:

| When | The runtime | Event |
|---|---|---|
| a job ends accepted | writes each kept fact, keyed by the period that job held | `job.accepted` |
| a newer value is written to a period another job read | names each reader; the reader's end offers its follow-on, or a plan's `on.event` mints it | `fact.restated` |
| a job ends with a clock still running | keeps the clock on the subject, and asks the client to own the date until a steward's job joins | `clock.outlived` |
| the member of record's credential or coverage lapses | the seat turns vacant, and the custodian covers it, rung by rung, then the brand | `clock.vacant` |
| a canceled plan's last cycle ends | offers the client every open clock the brand or a member of record answered for | `clock.outlived` |
| the member of record withdraws | names a successor who holds the role's credential, who accepts it | `succession.started`, `succession.accepted` |

## In the apps

| App | Shows | Acts |
|---|---|---|
| [Customer portal](https://services.dev/docs/app/buyers.md#dates-periods-and-allowances) | each subject in your word for it: one row per period, each kept fact with the job that wrote it, who is of record, and its dates | keep a running date with the steward Service, or sign that it is yours |
| [Delivery portal](https://services.dev/docs/app/members.md) | the subjects the member is of record on, with the dates they answer for | withdraw, naming a successor |
| [Owner console](https://services.dev/docs/operate/dates.md) | a seat of record left vacant, and any date on a subject no one answers for | assign a member who holds the credential |

## Money and plan

A subject has no price of its own: each job on it, and its steward, charges on its own terms. It keeps the plan it was ordered under, so its clocks run to their end after a downgrade. Subjects, periods and kept facts run live on [Launch](https://services.dev/docs/money/plans.md#launch); the custodian ladder and a steward on [Team](https://services.dev/docs/money/plans.md#team). Every kind of time, by plan: [Keeping time](https://services.dev/docs/service/time.md#plans).

## Seven surfaces

| Op | API | CLI | MCP | SDK | Site | Docs | App |
|---|---|---|---|---|---|---|---|
| `subject.list` | `GET /subjects`, `GET /owner/subjects` | `subjects list` | never: an agent reads its clocks with list_clocks | never: the SDK reads its clocks with clocks.list() |  | 1 error page | customer portal, owner console |
| `subject.get` | `GET /subjects/:id`, `GET /owner/subjects/:id` | `subjects get <subject>` | never: an agent reads its clocks with list_clocks | never: the SDK reads its clocks with clocks.list() |  | 1 error page | customer portal, owner console |
| `subject.withdraw` | `POST /subjects/:id/withdraw` | `subjects withdraw <subject> <successor>` | never: a person acts | never: a person acts |  | 2 error pages | delivery portal |
| `subject.calendar` | `GET /subjects/:id/calendar`, `.ics` | `subjects calendar <subject>` | never: an agent reads the dates with list_clocks | never: the SDK reads the dates with clocks.list() |  |  | customer portal |
| `member.subjects` | `GET /me/subjects` | `me subjects` | never: a person acts | never: a person acts |  |  | delivery portal |

Objects: `sbj_`.
Events: `fact.restated`, `succession.started`, `succession.accepted`.

[Operations](https://services.dev/docs/reference/operations.md) has every column of every row.

## Refusals

| Code | Category | HTTP | Why |
|---|---|---|---|
| [`not_found`](https://services.dev/docs/reference/errors.md#not_found) | invalid_request | 404 | Nothing has that id. |

| `check` gate | Why |
|---|---|
| [`clock_unowned`](https://services.dev/docs/reference/check/clock_unowned.md) | A clock's owner can never resolve: a role the Service doesn't have, or of_record with no member of record on its subject. It would run vacant, with no one to answer for the date. |

| When the file won't load | Thrown by | Why |
|---|---|---|
| [`writes_unknown`](https://services.dev/docs/reference/definition-errors.md#writes_unknown) | `Service()` | Service Glossary: subject.keeps.glossary is written from terms, which isn't an output. A kept fact is written from the output: glossary. |

## In the example brands

The lines that use it in the example brands, as their files write them, and what `check` read aloud for each file.

```ts title="legal · services/docket-watch.ts · 10 lines"
  subject: { kind: 'patent family', key: 'matter' },
```

```txt title="npx services.dev check · captured/legal-walk.txt"
services/docket-watch.ts · 10 lines · $12 per month, charged at the start of each month.
  steps   check · ai → ack · patent watcher → review · ai · adversarial
```

[The legal example](https://services.dev/docs/examples/legal.md) runs every one of its files, captured.

```ts title="tax · services/annual-return.ts · 45 lines"
  subject: {
    kind: 'taxpayer', key: 'ein', period: { of: 'tax_year', ends: job => job.input.year_end ?? '12-31' },
    of_record: { role: 'signer', named_at: 'sign', custodian: ['signer', 'brand'], until: 'assessment' },
    keeps: { carryforwards: 'carryforwards', tax: 'tax', paid: 'payment_usd', first_installment: 'first_installment' }, steward: carryforwardWatch,
  },
```

```txt title="npx services.dev check · captured/tax-check.txt"
services/annual-return.ts · 45 lines · $1,800 per job, filed 5 days before its deadline, 20% off if late, or we pay the penalty and its interest, up to $5,000, after we ask the IRS to take it off, charged when file is accepted.
  steps   consent · owner · consent · §7216, may disclose to a preparer outside the US and a lender you name → organizer · owner · upload → compute · code · cites depreciation and carryforward schedules → draft · ai · cites the workpapers and the records the organizer took → check · ai · adversarial · 3 lenses → sign · signer · * · stamped in the paid-preparer block · 3-item checklist → authorize · owner · sign · Form 8879-CORP or 8879-PE → file · IRS and states → prepare · preparer · * · 1-item checklist · routed → tentative · code · cites the year as the ledger stands, less the estimates paid · routed → pay · Treasury and states · routed → extend · IRS and states · routed
```

[Two clients](https://services.dev/docs/examples/tax/clients.md#a-client-who-changes) change, amend and leave in apis.tax.

[A year, and the next](https://services.dev/docs/examples/tax/years.md) reads one subject's facts across two years.

---

- Up: [Service()](https://services.dev/docs/service.md)
- Previous: [Clocks](https://services.dev/docs/service/clocks.md)
- Next: [One job per period](https://services.dev/docs/service/periods.md)
