factoryhub 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/README.md +9 -3
  2. package/factories/index.json +231 -0
  3. package/factories/newsletter-note/README.md +11 -0
  4. package/factories/newsletter-note/agents/reviewer/agent.md +10 -0
  5. package/factories/newsletter-note/agents/writer/agent.md +11 -0
  6. package/factories/newsletter-note/factory.yaml +12 -0
  7. package/factories/newsletter-note/flow.yaml +45 -0
  8. package/factories/newsletter-note/mindburn.tests.yaml +80 -0
  9. package/factories/release-desk/README.md +13 -0
  10. package/factories/release-desk/agents/editor/agent.md +10 -0
  11. package/factories/release-desk/agents/reviewer/agent.md +10 -0
  12. package/factories/release-desk/factory.yaml +13 -0
  13. package/factories/release-desk/flow.yaml +67 -0
  14. package/factories/release-desk/mindburn.tests.yaml +163 -0
  15. package/factories/short-video-cut/README.md +11 -0
  16. package/factories/short-video-cut/agents/reviewer/agent.md +10 -0
  17. package/factories/short-video-cut/agents/writer/agent.md +11 -0
  18. package/factories/short-video-cut/factory.yaml +13 -0
  19. package/factories/short-video-cut/flow.yaml +46 -0
  20. package/factories/short-video-cut/mindburn.tests.yaml +80 -0
  21. package/factories/video-facts/README.md +11 -0
  22. package/factories/video-facts/agents/analyst/agent.md +10 -0
  23. package/factories/video-facts/agents/reviewer/agent.md +10 -0
  24. package/factories/video-facts/factory.yaml +13 -0
  25. package/factories/video-facts/flow.yaml +39 -0
  26. package/factories/video-facts/mindburn.tests.yaml +34 -0
  27. package/factories/video-to-posts/README.md +35 -0
  28. package/factories/video-to-posts/agents/analyst/agent.md +10 -0
  29. package/factories/video-to-posts/agents/reviewer/agent.md +10 -0
  30. package/factories/video-to-posts/agents/writer/agent.md +11 -0
  31. package/factories/video-to-posts/factory.yaml +12 -0
  32. package/factories/video-to-posts/flow.yaml +62 -0
  33. package/factories/video-to-posts/mindburn.tests.yaml +34 -0
  34. package/factories/week-of-posts/README.md +11 -0
  35. package/factories/week-of-posts/agents/reviewer/agent.md +10 -0
  36. package/factories/week-of-posts/agents/writer/agent.md +11 -0
  37. package/factories/week-of-posts/factory.yaml +12 -0
  38. package/factories/week-of-posts/flow.yaml +46 -0
  39. package/factories/week-of-posts/mindburn.tests.yaml +80 -0
  40. package/package.json +15 -2
  41. package/packages/cli/package.json +1 -1
  42. package/packages/cli/src/commands/export.mjs +0 -1
  43. package/packages/cli/src/commands/get.mjs +0 -1
  44. package/packages/cli/src/commands/inspect.mjs +0 -1
  45. package/packages/cli/src/commands/list.mjs +1 -6
  46. package/packages/cli/src/commands/login.mjs +161 -0
  47. package/packages/cli/src/commands/logout.mjs +43 -0
  48. package/packages/cli/src/commands/mcp.mjs +0 -1
  49. package/packages/cli/src/commands/new.mjs +0 -1
  50. package/packages/cli/src/commands/pack.mjs +0 -1
  51. package/packages/cli/src/commands/publish.mjs +62 -0
  52. package/packages/cli/src/commands/pull.mjs +59 -0
  53. package/packages/cli/src/commands/push.mjs +115 -0
  54. package/packages/cli/src/commands/resume.mjs +0 -1
  55. package/packages/cli/src/commands/run.mjs +182 -20
  56. package/packages/cli/src/commands/runs.mjs +52 -0
  57. package/packages/cli/src/commands/search.mjs +0 -1
  58. package/packages/cli/src/commands/validate.mjs +1 -3
  59. package/packages/cli/src/commands/whoami.mjs +34 -0
  60. package/packages/cli/src/hosted.mjs +159 -0
  61. package/packages/cli/src/main.mjs +80 -14
  62. package/packages/cli/src/tool-sources.mjs +272 -0
  63. package/packages/cli/src/util.mjs +37 -0
  64. package/packages/engine/lib/credentials.mjs +121 -0
  65. package/packages/engine/lib/hosted.mjs +119 -0
  66. package/packages/engine/lib/hub.mjs +376 -24
  67. package/packages/engine/lib/nango.mjs +157 -0
  68. package/packages/engine/lib/node.mjs +161 -33
  69. package/packages/engine/lib/providers.mjs +8 -3
  70. package/packages/engine/lib/run.mjs +70 -9
  71. package/packages/engine/lib/text.mjs +5 -0
  72. package/packages/engine/package.json +8 -2
  73. package/packages/engine/src/factory-engine.mjs +415 -68
  74. package/packages/engine/src/flow-sources.mjs +135 -0
  75. package/packages/engine/src/mcp-client.mjs +429 -0
  76. package/packages/engine/src/run.mjs +81 -5
  77. package/packages/engine/src/tool-sources.mjs +566 -0
  78. package/packages/mcp/package.json +1 -1
  79. package/packages/mcp/src/server.mjs +47 -17
  80. package/packages/sdk/package.json +28 -0
  81. package/packages/sdk/src/index.d.ts +163 -0
  82. package/packages/sdk/src/index.mjs +266 -0
  83. package/spec/README.md +7 -1
  84. package/spec/factory-format.md +264 -19
  85. package/spec/schema/factory.yaml.json +815 -0
  86. package/spec/schema/flow.schema.json +24 -2
  87. package/spec/skill-export.md +3 -0
@@ -1,7 +1,11 @@
1
1
  # The factory format, v1alpha1
2
2
 
3
3
  Status: alpha. This document describes the format as the reference engine in
4
- [`packages/engine`](../packages/engine) reads and runs it at this commit.
4
+ [`packages/engine`](../packages/engine) reads and runs it at this commit,
5
+ except the pack v0 additions that
6
+ [section 12](#pack-v0-additions-not-yet-in-the-reference-engine) lists and
7
+ the single-file form of [section 13](#13-the-single-file-form): the format
8
+ defines them, and the reference engine does not implement them yet.
5
9
  Changes go through the [RFC process](rfcs/README.md). The key words MUST,
6
10
  MUST NOT, SHOULD and MAY are used as in RFC 2119.
7
11
 
@@ -24,6 +28,7 @@ Contents:
24
28
  10. [Companion files](#10-companion-files)
25
29
  11. [What the format leaves out](#11-what-the-format-leaves-out)
26
30
  12. [JSON Schemas and conformance](#12-json-schemas-and-conformance)
31
+ 13. [The single-file form](#13-the-single-file-form)
27
32
 
28
33
  ## 1. Files
29
34
 
@@ -33,7 +38,8 @@ flow.yaml inputs, steps, who does each, what each needs
33
38
  agents/<name>/agent.md one file per agent: its role and standing instructions
34
39
  ```
35
40
 
36
- These three are the core. A factory MAY carry companion files. Only paths of
41
+ These three are the core. A factory MAY carry companion files. The same files
42
+ can also be written as one YAML file ([section 13](#13-the-single-file-form)). Only paths of
37
43
  these forms are part of a factory; a runner MUST ignore every other path:
38
44
 
39
45
  | Path | What it is |
@@ -230,7 +236,7 @@ done:
230
236
  | Field | Meaning |
231
237
  |---|---|
232
238
  | `flow` | `mindburn.flow/v1`. Another value is read with a warning |
233
- | `requires` | execution features the flow depends on: `scoped-context`, `multiple-outputs`. A runner MUST refuse a flow that requires a feature it does not know |
239
+ | `requires` | execution features the flow depends on: `scoped-context`, `multiple-outputs`, `blocking-checks` ([5.3](#blocking-checks)). A runner MUST refuse a flow that requires a feature it does not know |
234
240
  | `title` | the human title. Default: the name, with hyphens as spaces |
235
241
  | `author`, `license`, `time` | text: who wrote it, its license (an SPDX identifier is recommended), how long a run takes |
236
242
  | `version` | the factory's version, as text. Quote it: `"1.0.0"`. Default `1.0.0` |
@@ -287,6 +293,7 @@ input names; write them normalized, such as `check_kit`.
287
293
  | `checks` | for a `check`: the work or deliver step it reviews |
288
294
  | `shows` | for an `approve`: the work or deliver step it shows the person |
289
295
  | `rounds` | how often a check or person may send the work back: 0 to 5. Default 1 for a check, 3 for an approval |
296
+ | `on_fail` | for a `check` only: `flag` (default) or `stop`, what happens when the work still fails after its redos ([blocking checks](#blocking-checks)) |
290
297
  | `tools` | tools the step may use ([5.5](#55-tools)) |
291
298
  | `context` | scoped context ([5.7](#57-scoped-context)) |
292
299
  | `criteria` | a list of statements this step's result is held to, in place of `done` |
@@ -294,7 +301,7 @@ input names; write them normalized, such as `check_kit`.
294
301
  | Kind | What happens |
295
302
  |---|---|
296
303
  | `work` | The agent does one job and hands its result on. |
297
- | `check` | A reviewer judges the result named in `checks` against the criteria in `task`. If it fails, that work goes back to be redone with the reasons, and everything built on it runs again. After the allowed redos the run continues and the receipt says the check failed (`flagged`). A reviewer that returns malformed output gets one format-repair attempt; if that also fails, the result is `unreviewed`, never passed. |
304
+ | `check` | A reviewer judges the result named in `checks` against the criteria in `task`. If it fails, that work goes back to be redone with the reasons, and everything built on it runs again. After the allowed redos the run continues and the receipt says the check failed (`flagged`); with `on_fail: stop` the work is held for the owner instead ([blocking checks](#blocking-checks)). A reviewer that returns malformed output gets one format-repair attempt; if that also fails, the result is `unreviewed`, never passed. |
298
305
  | `approve` | The run stops and shows the person the result named in `shows`. They approve; say what to change, and that work is redone; or reject, which ends the run with no result. Nothing else counts as a decision, and no runner makes one for them. |
299
306
  | `deliver` | The agent writes the final deliverable. |
300
307
 
@@ -303,6 +310,34 @@ is the first step in its `needs` that is earlier work or deliver, otherwise
303
310
  the last earlier work or deliver step. The subject MUST be work or deliver
304
311
  that the step depends on, directly or through other steps.
305
312
 
313
+ #### Blocking checks
314
+
315
+ Requires `requires: [blocking-checks]`. A `check` that says `on_fail: stop`
316
+ does not let refused work through:
317
+
318
+ - When the work still fails after its redos (`flagged`), or the reviewer gives
319
+ no valid verdict (`unreviewed`), the run **holds** it for the owner
320
+ ([section 7](#7-how-a-run-works)). Nothing built on it runs and nothing is
321
+ delivered; steps that do not depend on it finish first. The owner releases
322
+ the work as it is (`release`) or discards it and ends the run (`discard`).
323
+ - A blocking check gates its subject: every step that needs the subject, other
324
+ than another check of it, waits until the check passes or the owner releases
325
+ the work, whether it needs the check or only the subject. A flow in which the
326
+ blocking check itself needs such a step is invalid, as it would wait for
327
+ itself.
328
+ - `on_fail` is allowed only on a `check`, with the value `stop` or `flag`.
329
+ Without it, or with `flag`, the check behaves as described above.
330
+ - A flow with an `on_fail: stop` check MUST declare
331
+ `requires: [blocking-checks]`, so a runner without blocking checks refuses
332
+ the flow ([5.1](#51-top-level-fields)) instead of delivering work its
333
+ checker refused.
334
+
335
+ An agent following the factory without a runner, such as an exported skill,
336
+ gets this text for the check in place of the "carry on" text, word for word:
337
+
338
+ - with `rounds` 1 or more: `If it fails, send it back to be redone <once|up to N times> with the reasons, and redo every step that used it. If it still fails, stop and ask the person; do not deliver. Nothing that uses "<subject title>" starts until this check passes or the person lets the work through.`
339
+ - with `rounds: 0`: `If it fails, do not redo it. Stop and ask the person; do not deliver. Nothing that uses "<subject title>" starts until this check passes or the person lets the work through.`
340
+
306
341
  Fields that other harnesses use for tools and connections (`mcp`,
307
342
  `mcpServers`, `mcp_servers`, `connectors`, `plugins`, `skills`) are read as
308
343
  tools this step needs ([5.5](#55-tools)). Any other unknown field is kept, has
@@ -394,14 +429,48 @@ composition:
394
429
  compiler: mindburn.compose/v1
395
430
  intent: The person's original request.
396
431
  sources:
397
- - { id: research, name: market-research, version: "1.0.0", sha256: <64 hex> }
432
+ - { id: research, name: market-research, title: Market research, version: "1.0.0", sha256: <64 hex> }
398
433
  unmetCapabilities: []
399
434
  ```
400
435
 
401
- All four fields are required and nothing else is allowed. `composeFactories`
402
- in [`compose.mjs`](../packages/engine/src/compose.mjs) produces these
403
- factories from members pinned by hash; the result is an ordinary factory and
404
- needs no registry to run. A source hash establishes identity, not trust.
436
+ All four fields are required and nothing else is allowed. Each source has
437
+ `id`, `name`, `version` and `sha256`, and MAY have `title`, the member
438
+ factory's title, at most 120 characters. `composeFactories` in
439
+ [`compose.mjs`](../packages/engine/src/compose.mjs) produces these factories
440
+ from members pinned by hash; the result is an ordinary factory and needs no
441
+ registry to run. A source hash establishes identity, not trust, and a
442
+ `composition` block is text anyone can write: a runner names the members of a
443
+ run in its receipt only for a composition it compiled itself
444
+ ([section 8](#8-receipts)).
445
+
446
+ Each member's steps run under the member's id (member `posts`, step `write`
447
+ runs as `posts_write`). A compiler:
448
+
449
+ - refuses member ids where one begins with another followed by `_`, so every
450
+ step belongs to exactly one member;
451
+ - keeps a member check's `on_fail`, and adds `blocking-checks` to `requires`
452
+ when any member check says `on_fail: stop`;
453
+ - MAY take `maxCalls`, a positive whole number, and then refuses a
454
+ composition whose worst-case run needs more model calls (`worstCaseCalls`).
455
+ It returns `worstCaseCalls` with the result. The worst case counts the
456
+ calls one run can make: a work or deliver step with tools may take every
457
+ model turn (8 in the reference runner), one without tools makes one call; a
458
+ check makes two, its verdict and one format repair; an approval makes none.
459
+ Each check or approval may send its subject back as often as its `rounds`
460
+ allow, and each time the subject, the sender and every step built on the
461
+ subject that does not wait for the sender run again.
462
+
463
+ A member MAY carry `rounds: { <step>: <n> }`, with the member's own step
464
+ names. It lowers the revision rounds of its own check or approve steps in the
465
+ composed flow, so a composition fits `maxCalls` without changing the member.
466
+ The compiler refuses something other than a map, a step that is not one of
467
+ the member's checks or approvals, and a fraction, a negative number or more
468
+ rounds than the step has. The member's pin is unchanged.
469
+
470
+ A member's `mindburn.tests.yaml` tests that member alone, so the compiler
471
+ leaves it out of the composed factory. The member's pin stays the bundle hash
472
+ of all its files, the tests file included. Any other file the compiler cannot
473
+ compile is refused.
405
474
 
406
475
  ### 5.10 Limits
407
476
 
@@ -456,14 +525,30 @@ run; an agent can follow its foreman.
456
525
  differs for every occurrence, so a delayed decision cannot land on text that
457
526
  changed since or on a later review of the same text. With no decision the
458
527
  runner returns the run still waiting.
459
- 6. A provider answer that is truncated, incomplete, or asks for a tool the
528
+ 6. A [blocking check](#blocking-checks) that holds work makes the run wait the
529
+ same way, once nothing else can run, with
530
+ `approval: { kind: "blocked", step, title, artifact, artifact_sha256, ask, issues, note, choices: ["release", "discard"], id, revisionsLeft: 0 }`.
531
+ The only answers are `release` and `discard`, with `seen` and the
532
+ approval's id as above; `approve`, `revise` and `reject` are refused at a
533
+ hold, and `release` and `discard` at an approval. `release` lets the run go
534
+ on and the receipt flags the release. `discard` ends the run as `stopped`
535
+ with nothing delivered. A release answers one attempt of its check: when the
536
+ work is redone and the check runs again, the new verdict stands. A runner
537
+ never delivers a run whose blocking check refused work the owner did not
538
+ release; such a run fails instead. An agent app acting for the owner is
539
+ shown the hold's `artifact_sha256` and `issues`, never the held text, and
540
+ cannot release or discard it.
541
+ 7. A provider answer that is truncated, incomplete, or asks for a tool the
460
542
  step was not offered stops the run with a reason. Failed page reads, missing
461
543
  search and partial pages are recorded as limitations when usable work
462
544
  continues.
463
- 7. The reference runner's budgets: 60 model calls and 20 page reads per run, 4
464
- searches and 8 model turns per step, 14,000 characters of text per page.
465
- 8. A finished run ends with the deliverable and a [receipt](#8-receipts). A
466
- run that was rejected at a decision has no deliverable and no receipt.
545
+ 8. The reference runner's budgets: 60 model calls and 20 page reads per run, 4
546
+ searches and 8 model turns per step, 14,000 characters of text per page,
547
+ read from at most 1,500,000 bytes of a text, HTML, XML or JSON page.
548
+ 9. A finished run ends with the deliverable and a [receipt](#8-receipts). A
549
+ run that was rejected at a decision has no deliverable and no receipt. A
550
+ run whose owner discarded held work has no deliverable and a receipt whose
551
+ `outcome` says so.
467
552
 
468
553
  A repeated run follows the same declared workflow; model outputs can differ.
469
554
 
@@ -478,11 +563,41 @@ A completed run produces a receipt (`kind: factory-run-receipt`, `v: 2`):
478
563
  | `inputs_sha256` | hash of the canonical JSON of the accepted inputs |
479
564
  | `runner` | mode, provider and model |
480
565
  | `started`, `finished` | ISO 8601 times |
481
- | `steps` | every step attempt: status, tools used and targets, verdicts, limitations, decisions with `seen_sha256` and `approval_id`, `output_sha256`; for reviews, the target hash, reviewer provider and model, input and evidence hashes and the request hash |
482
- | `flags` | what the person should know: failed checks, unreviewed results, limitations |
566
+ | `steps` | every step attempt: status, tools used and targets, verdicts, limitations, decisions with `seen_sha256`, `approval_id` and `decided_by`, a check's `on_fail`, `output_sha256`; for reviews, the target hash, reviewer provider and model, input and evidence hashes and the request hash |
567
+ | `flags` | what the person should know: failed checks, unreviewed results, limitations, released holds |
483
568
  | `outputs` | with declared outputs: each output's hash and review |
484
- | `deliverable_review` | `passed`, `limited` (a passing review with missing evidence), `flagged`, `unreviewed` or `not_checked`, for the delivered bytes |
485
- | `deliverable_sha256`, `usage` | hash of the deliverable; model calls and tokens |
569
+ | `members` | for a composition the runner compiled itself: one entry per member (below) |
570
+ | `outcome` | for a run whose owner discarded held work: `{ status: "blocked", by: "owner", step, target, decision: "discard", at, decided_by }` |
571
+ | `deliverable_review` | `passed`, `limited` (a passing review with missing evidence), `flagged`, `unreviewed` or `not_checked`, for the delivered bytes; `flagged` whenever a release of held work is in effect, whatever a later check says; `not_delivered`, with `sha256: null` and no checks, when the owner discarded held work |
572
+ | `deliverable_sha256`, `usage` | hash of the deliverable, `null` when nothing was delivered; model calls and tokens. Each entry of `usage.metering.calls` names the `step` it was made for |
573
+
574
+ **Decisions.** Every decision record (an approval, and the owner's answer to
575
+ a hold) carries `decided_by: { account, surface }`. `account` is a stable,
576
+ salted hash of the deciding account, never its id, or `null` when no account
577
+ stands behind the decision (a run key). `surface` is how the decision arrived:
578
+ `web`, `mcp`, `a2a`, `email` or `api`. It is a label taken from the request,
579
+ not proof of the channel; who had authority is `account`. No handle or name is
580
+ signed: a reader looks up who an account is while it exists.
581
+
582
+ **Holds.** The owner's answer to a [blocking check](#blocking-checks) is a
583
+ step of `kind: hold`: `{ status: released | discarded, decision, target, seen_sha256, approval_id, decided_by }`,
584
+ kept apart from the check record it answers, which keeps its `flagged` or
585
+ `unreviewed` status.
586
+
587
+ **Members.** A runner adds `members` only for a composition it compiled
588
+ itself, from the sources it hashed, and never from a bundle's own
589
+ `composition` block, which anyone can write. Each entry, in compile order:
590
+ `{ id, name, title, version, source_sha256, status, steps, send_backs, released, usage }`.
591
+ `title` is at most 120 characters; `steps` are the member's namespaced step
592
+ ids; `send_backs` counts its check records that read `sent_back`; `released`
593
+ is true when the owner released work one of its blocking checks refused and
594
+ that check has not run again since; `usage` has the shape of a run's `usage`,
595
+ holding the calls made for its steps. A receipt's `status` is `done`,
596
+ `blocked` (held work, discarded or left held when the run ended), `failed`,
597
+ `stopped` or `skipped`; a runner's live view MAY also show `pending`,
598
+ `running` and `waiting`. Receipts keep counters, not prices.
599
+
600
+ A discarded run's receipt verifies no result: check only its signature.
486
601
 
487
602
  A runner MAY sign a receipt: `signature: { key_id, sig }`, an Ed25519
488
603
  signature (base64url) over the canonical JSON of the receipt without its
@@ -601,6 +716,10 @@ engine on the fixtures in [`fixtures/`](fixtures):
601
716
  - `semantic/`: the schemas accept them and the engine rejects them, by a rule
602
717
  of [section 6](#6-validity-and-runnability) that a per-file schema cannot
603
718
  express, such as a missing agent file or a circle of steps.
719
+ - `unimplemented/`: the schemas accept them and the engine refuses them,
720
+ because they require an execution feature it does not implement yet
721
+ (`blocking-checks`). A runner MUST refuse such a flow
722
+ ([5.1](#51-top-level-fields)); the test checks that this is the only reason.
604
723
  - `lenient/`: the engine accepts them and the schemas reject them. The engine
605
724
  converts or ignores values the format does not define. The fixtures cover:
606
725
  an uppercase `kind`; an unknown input `type` (read as `text`); a number
@@ -609,13 +728,139 @@ engine on the fixtures in [`fixtures/`](fixtures):
609
728
  warning); `rounds` outside 0 to 5 (clamped); an uppercase `agent` (read in
610
729
  lowercase); `checks` on a `work` step (ignored); a `done` item that YAML
611
730
  reads as a map (joined into text); an input given as plain text (read as
612
- its help); step names that are not written normalized. The engine also
731
+ its help); step names that are not written normalized; `on_fail: stop`
732
+ without `requires: [blocking-checks]` (the engine at this commit ignores
733
+ `on_fail` with a warning; it will reject this once it implements blocking
734
+ checks). The engine also
613
735
  accepts, without fixtures here, non-boolean `required` and `optional`,
614
736
  non-integer `rounds` (the default is used), and scalars where
615
737
  `repositories`, `agentDefaults`, `author` or `tags` items belong. Authors
616
738
  SHOULD NOT rely on any of these; a future version may reject them.
617
739
 
740
+ [`schema/factory.yaml.json`](schema/factory.yaml.json) (JSON Schema draft-07)
741
+ describes the single-file form of [section 13](#13-the-single-file-form); its
742
+ fixtures and test are listed there.
743
+
618
744
  A conforming factory validates against both schemas and passes the rules of
619
745
  [section 6](#6-validity-and-runnability). The engine is the reference: where
620
746
  this document and the engine disagree, that is a bug in one of them, and an
621
747
  issue is welcome.
748
+
749
+ ### Pack v0 additions not yet in the reference engine
750
+
751
+ The format defines these, and the engine and CLI at this commit do not
752
+ implement them yet:
753
+
754
+ - [blocking checks](#blocking-checks): `on_fail`, `requires: [blocking-checks]`,
755
+ the gate on the subject, holds and their answers (section 7, item 6), and the
756
+ export text. The engine refuses a flow that requires `blocking-checks`, as
757
+ 5.1 requires, and ignores `on_fail` elsewhere with a warning;
758
+ - in [5.9](#59-composition): recording `title`, the nested member-id refusal,
759
+ carrying `on_fail`, `maxCalls`, `worstCaseCalls`, a member's `rounds` and
760
+ leaving out a member's tests file;
761
+ - in [section 8](#8-receipts): `members`, `outcome`, `decided_by`, `on_fail`,
762
+ `kind: hold`, `deliverable_review.status: not_delivered`, the `flagged`
763
+ status for a release in effect, and `usage.metering.calls[].step`.
764
+
765
+ The schemas already describe the new fields: `on_fail` (only on a `check`,
766
+ `stop` or `flag`), `blocking-checks` in `requires` (required by any
767
+ `on_fail: stop` check), and `title` (at most 120 characters) in a composition
768
+ source.
769
+
770
+ [RFC 0020](rfcs/0020-pack-v0.md) is where these additions are discussed and
771
+ decided. It also proposes who paid for a run and for each call (`funding`,
772
+ `payer`), which this document does not define until the RFC settles them.
773
+
774
+ ## 13. The single-file form
775
+
776
+ Proposed in [RFC 0023](rfcs/0023-single-file-factory.md), a draft, which
777
+ gives the full rules. The reference engine and CLI at this commit do not read
778
+ this form yet: `factoryhub validate` and `factoryhub run` read factory folders
779
+ and bundle `.json` files, and `factoryhub push` refuses a single file.
780
+
781
+ A whole factory MAY be written as one YAML file, `factory.yaml`, with
782
+ `format: mindburn.factory.yaml/v1` on a line of its own. Each top-level
783
+ section is one file of the bundle, and its text is that file's exact bytes:
784
+
785
+ ```yaml
786
+ # yaml-language-server: $schema=https://raw.githubusercontent.com/Mindburn-Labs/factoryhub/main/spec/schema/factory.yaml.json
787
+ format: mindburn.factory.yaml/v1
788
+
789
+ factory:
790
+ schemaVersion: v1alpha1
791
+ name: one-step-summary
792
+ description: Writes a three-sentence summary of the text you give it.
793
+
794
+ flow:
795
+ flow: mindburn.flow/v1
796
+ inputs:
797
+ text:
798
+ type: longtext
799
+ help: The text to summarise.
800
+ steps:
801
+ summary:
802
+ agent: writer
803
+ task: Summarise the text in three sentences.
804
+
805
+ agents:
806
+ writer:
807
+ description: Writes plain summaries.
808
+ instructions: |
809
+ You write short, plain summaries. You add nothing the text does not say.
810
+ ```
811
+
812
+ | Section | Bundle file |
813
+ |---|---|
814
+ | `format` | none; required, `mindburn.factory.yaml/v1` |
815
+ | `factory`, `flow` | `factory.yaml`, `flow.yaml` |
816
+ | `agents` | `agents/<name>/agent.md`, one entry per agent: its settings, then `instructions: \|` |
817
+ | `tests`, `requirements`, `package` | `mindburn.tests.yaml`, `mindburn.requirements.yaml`, `mindburn.package.yaml` |
818
+ | `samples` | `examples/<path>`, one entry per file |
819
+ | `files` | every other file, by its path |
820
+
821
+ - **Sections are files.** The lines of a YAML section, less two spaces of
822
+ indent, are its file, plus one final line break; comments and formatting
823
+ inside a section are the file's own bytes. An agent entry is its
824
+ frontmatter settings, then its body under `instructions: |` (or `|-` for no
825
+ final line break). A file a block cannot carry byte for byte is one JSON
826
+ string, its exact text, on its key's line.
827
+ - **The coordinator.** A writer leaves out `agents/foreman/agent.md` while it
828
+ is exactly what the engine writes from the flow (`withForeman`), and a
829
+ reader writes it again. An authored or stale foreman is kept as written;
830
+ `foreman: ~` says the bundle has none.
831
+ - **Presentation.** Comments and blank lines between sections, section order,
832
+ comments on a section's own line and top-level `x-` keys are no file's
833
+ content. They never change the bundle or its hash.
834
+ - **Unknown keys.** Any other unknown top-level key is an error at its line,
835
+ never dropped. Inside a section every key is kept byte for byte, whether a
836
+ runner reads it or not; `requires` gates features exactly as in
837
+ [5.1](#51-top-level-fields).
838
+ - **Identity.** A single file is a view of its bundle. Its hash is the
839
+ [bundle hash](#9-identity-and-the-transfer-bundle) of the bundle it expands
840
+ to; no byte outside the files counts. Writing a bundle as one file and
841
+ reading it back MUST give the same bundle.
842
+ - **Size.** A file in `samples` or `files` over 32 KiB (32,768 UTF-8 bytes)
843
+ MAY be written as a reference, `{ sha256: "<64 hex>", bytes: <n> }`: the
844
+ same file of the version the text came from. A reader resolves it only
845
+ against that version, and an editor shows it read-only. The bundle the file
846
+ expands to keeps the limits of [section 9](#9-identity-and-the-transfer-bundle).
847
+ - **Versions.** `format` versions the layout; `schemaVersion` and `flow` keep
848
+ versioning their files. A reader MUST refuse a `format` it does not know.
849
+ A file with a line `format: mindburn.factory.yaml/v1` at column 1 is a
850
+ single file, not a bundle's `factory.yaml`.
851
+
852
+ [`schema/factory.yaml.json`](schema/factory.yaml.json) (draft-07) gives
853
+ editors completion, hover text and errors. It is open wherever a file is and
854
+ does not decide validity, which the reader and
855
+ [section 6](#6-validity-and-runnability) do. Its `$id`,
856
+ `https://factory.mindburn.org/schemas/factory.yaml.json`, is its stable
857
+ address on the factoryhub site, which generates the same document. That site
858
+ is in private preview, so point editors at this repository's copy, as the
859
+ example's first line does ([editor setup](../docs/editor-setup.md)).
860
+
861
+ [`fixtures/single-file/`](fixtures/single-file) holds `valid/` (the schema
862
+ accepts them and each is a valid factory; two are house factories, equal to
863
+ their folders byte for byte), `invalid/` (the schema rejects each at the place
864
+ it names) and `reader/` (the schema accepts them, and the rules above reject
865
+ them). [`test/single-file.test.mjs`](test/single-file.test.mjs) checks them,
866
+ and checks the reference engine against them once it reads this form.