Skip to content

A workflow's subject is one word, read off the class or off the run - #142

Merged
czpython merged 1 commit into
mainfrom
subject-descriptor-and-declarative-schemas
Jul 28, 2026
Merged

A workflow's subject is one word, read off the class or off the run#142
czpython merged 1 commit into
mainfrom
subject-descriptor-and-declarative-schemas

Conversation

@czpython

Copy link
Copy Markdown
Owner

Three things the subject arc left behind.

subject is one word, answering twice

A workflow declares what its runs are about. The declaration and the run's live
subject share the name, so __init_subclass__ moved the class attribute out of
the way and kept a private copy:

del cls.subject
cls._subject_class = declared

Which left Build.subject answering None while Build is plainly about a
WorkItem, and two modules reaching across for the truth — signals.py and
extensions/base.py both read workflow_class._subject_class to derive a
subscriber's filter and an extension's mounted boards.

A descriptor is what the two readings were asking for. The declaration becomes
one, and the same word answers the workflow with the kind of thing and a run
with the particular one:

class _DeclaredSubject:
    def __get__(self, run, owner):
        if run is None:
            return self.subject_class
        ...

Build.subject is WorkItem is true now, _subject_class is gone, and the two
cross-module private reads are workflow.subject.

Presence in cls.__dict__ still decides, not the value: an explicit
subject = None is a declaration of nothing, and stays an error.

The proof extension said "summary" three times

field_notes is what an extension author copies from, and it spelled one word
over three unrelated things: Note.summary (the agent's prose),
Note.get_summary() (the platform's board header), and two classes both named
NoteSummary — one an AgentOutput, one a SubjectSummary.

get_summary() / list_summaries() is the platform's pair and stays. The
domain word moves: a note has a body, and the agent writes its gist.

  • Note.summaryNote.gist, save_summary()save_gist()
  • contracts.NoteSummary(AgentOutput)GistOutput, returning gist
  • schemas.NoteSummary(SubjectSummary) keeps its name — it is the platform summary
  • the baseline migration, the prompt template and the agent's description follow

Two more builders that were copying fields

Same reduction as #139: a builder that only reads attributes off one object is
model_validate and a few aliases.

FeedItem.from_eventseq / at / kind alias to the event's id /
created_at / type, workflow becomes AliasPath("payload", "kind"), and
id is the f"event:{seq}" computed field DashboardItem.key already models.

AgentCallResponse.from_callaccount_username becomes
AliasPath("account", "username") and label a computed field. The two
derivations it reached for move onto the row that owns them:
AgentCall.get_live_status() is the live_status property, and
normalize_token_usage(call.cost_metadata) is AgentCall.token_usage, which
retires the _token_usage helper in schemas.py. status is typed
AgentCallStatus rather than a restated literal union, so the # type: ignore
the mismatch needed goes with it.

Both wire shapes are unchanged, field for field.

The from_* builders that stay are the ones that aren't field copies:
AgentCallFiles.from_call walks the filesystem, RunResponse.from_run takes a
second argument, HarnessResponse.from_row joins three sources,
SettingsFieldResponse.from_field reads a FieldInfo plus kwargs, and the four
from_settings are config constructors.

The declaration and the run's live subject shared the name, so
__init_subclass__ deleted the class attribute and kept a private copy —
leaving Build.subject as None and two modules reading _subject_class
across the wall. A descriptor answers both: the workflow with the kind of
thing, a run with the particular one.

field_notes spelled "summary" over three unrelated things — the agent's
prose, the platform's board header, and two classes of the same name. The
note's domain word is now its gist.

FeedItem.from_event and AgentCallResponse.from_call were copying fields
off one object, so they become model_validate and aliases. The two
derivations the latter reached for move onto AgentCall as live_status and
token_usage.
@czpython
czpython merged commit cec311a into main Jul 28, 2026
1 check passed
@czpython
czpython deleted the subject-descriptor-and-declarative-schemas branch July 28, 2026 17:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant