flyloft 0.1.0__tar.gz

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 (112) hide show
  1. flyloft-0.1.0/.github/workflows/ci.yml +59 -0
  2. flyloft-0.1.0/.github/workflows/python-publish.yaml +19 -0
  3. flyloft-0.1.0/.gitignore +6 -0
  4. flyloft-0.1.0/.python-version +1 -0
  5. flyloft-0.1.0/AGENTS.md +58 -0
  6. flyloft-0.1.0/CLAUDE.md +3 -0
  7. flyloft-0.1.0/PKG-INFO +10 -0
  8. flyloft-0.1.0/docs/ARCHITECTURE.md +265 -0
  9. flyloft-0.1.0/docs/VISION.md +240 -0
  10. flyloft-0.1.0/docs/design/stage-1.md +192 -0
  11. flyloft-0.1.0/docs/design/stage-2.md +122 -0
  12. flyloft-0.1.0/docs/plans/stage-1.md +5011 -0
  13. flyloft-0.1.0/docs/plans/stage-2.md +2831 -0
  14. flyloft-0.1.0/examples/live-config/clusters/slinky.toml +6 -0
  15. flyloft-0.1.0/examples/live-config/flyloft.toml +5 -0
  16. flyloft-0.1.0/examples/live-config/harnesses/claude.toml +3 -0
  17. flyloft-0.1.0/examples/live-config/harnesses/happy_path.toml +2 -0
  18. flyloft-0.1.0/examples/live-config/harnesses/opencode.toml +2 -0
  19. flyloft-0.1.0/examples/live-config/harnesses/strict_critic.toml +2 -0
  20. flyloft-0.1.0/examples/live-config/projects/toy-live.toml +12 -0
  21. flyloft-0.1.0/migrations/0001_initial.sql +104 -0
  22. flyloft-0.1.0/migrations/0002_finding_state.sql +7 -0
  23. flyloft-0.1.0/pyproject.toml +44 -0
  24. flyloft-0.1.0/src/flyloft/__init__.py +0 -0
  25. flyloft-0.1.0/src/flyloft/_version.py +24 -0
  26. flyloft-0.1.0/src/flyloft/cli/__init__.py +0 -0
  27. flyloft-0.1.0/src/flyloft/cli/main.py +196 -0
  28. flyloft-0.1.0/src/flyloft/config/__init__.py +0 -0
  29. flyloft-0.1.0/src/flyloft/config/load.py +61 -0
  30. flyloft-0.1.0/src/flyloft/config/models.py +68 -0
  31. flyloft-0.1.0/src/flyloft/publication/__init__.py +0 -0
  32. flyloft-0.1.0/src/flyloft/publication/body.py +28 -0
  33. flyloft-0.1.0/src/flyloft/publication/gh.py +48 -0
  34. flyloft-0.1.0/src/flyloft/publication/pr_body.md.j2 +59 -0
  35. flyloft-0.1.0/src/flyloft/publication/publisher.py +7 -0
  36. flyloft-0.1.0/src/flyloft/record/__init__.py +0 -0
  37. flyloft-0.1.0/src/flyloft/record/actions.py +294 -0
  38. flyloft-0.1.0/src/flyloft/record/consistency.py +64 -0
  39. flyloft-0.1.0/src/flyloft/record/db.py +9 -0
  40. flyloft-0.1.0/src/flyloft/record/migrate.py +21 -0
  41. flyloft-0.1.0/src/flyloft/record/models.py +189 -0
  42. flyloft-0.1.0/src/flyloft/record/store.py +324 -0
  43. flyloft-0.1.0/src/flyloft/record/views.py +57 -0
  44. flyloft-0.1.0/src/flyloft/runs/__init__.py +0 -0
  45. flyloft-0.1.0/src/flyloft/runs/askpass.sh +2 -0
  46. flyloft-0.1.0/src/flyloft/runs/client.py +18 -0
  47. flyloft-0.1.0/src/flyloft/runs/collect.py +37 -0
  48. flyloft-0.1.0/src/flyloft/runs/observe.py +21 -0
  49. flyloft-0.1.0/src/flyloft/runs/ssh.py +105 -0
  50. flyloft-0.1.0/src/flyloft/runs/staging.py +33 -0
  51. flyloft-0.1.0/src/flyloft/service/__init__.py +0 -0
  52. flyloft-0.1.0/src/flyloft/service/factory.py +13 -0
  53. flyloft-0.1.0/src/flyloft/service/tick.py +190 -0
  54. flyloft-0.1.0/src/flyloft/tools/__init__.py +0 -0
  55. flyloft-0.1.0/src/flyloft/tools/__main__.py +5 -0
  56. flyloft-0.1.0/src/flyloft/tools/server.py +146 -0
  57. flyloft-0.1.0/src/flyloft/turns/__init__.py +0 -0
  58. flyloft-0.1.0/src/flyloft/turns/assembly.py +130 -0
  59. flyloft-0.1.0/src/flyloft/turns/launch.py +137 -0
  60. flyloft-0.1.0/src/flyloft/turns/roles/__init__.py +20 -0
  61. flyloft-0.1.0/src/flyloft/turns/roles/critic.md.j2 +73 -0
  62. flyloft-0.1.0/src/flyloft/turns/roles/implementer.md.j2 +87 -0
  63. flyloft-0.1.0/src/flyloft/turns/substrates/__init__.py +35 -0
  64. flyloft-0.1.0/src/flyloft/turns/substrates/claude_code.py +72 -0
  65. flyloft-0.1.0/src/flyloft/turns/substrates/opencode.py +89 -0
  66. flyloft-0.1.0/src/flyloft/turns/substrates/scripted.py +94 -0
  67. flyloft-0.1.0/src/flyloft/workspace/__init__.py +0 -0
  68. flyloft-0.1.0/src/flyloft/workspace/git.py +67 -0
  69. flyloft-0.1.0/tests/__init__.py +0 -0
  70. flyloft-0.1.0/tests/conftest.py +180 -0
  71. flyloft-0.1.0/tests/e2e/__init__.py +0 -0
  72. flyloft-0.1.0/tests/e2e/test_live.py +59 -0
  73. flyloft-0.1.0/tests/e2e/test_stage1.py +149 -0
  74. flyloft-0.1.0/tests/e2e/test_stage2.py +119 -0
  75. flyloft-0.1.0/tests/fakes/__init__.py +0 -0
  76. flyloft-0.1.0/tests/fakes/cluster.py +54 -0
  77. flyloft-0.1.0/tests/fakes/publisher.py +18 -0
  78. flyloft-0.1.0/tests/fixtures/toyproject/BRIEF.md +9 -0
  79. flyloft-0.1.0/tests/fixtures/toyproject/config.json +1 -0
  80. flyloft-0.1.0/tests/fixtures/toyproject/train.py +47 -0
  81. flyloft-0.1.0/tests/scripts/__init__.py +0 -0
  82. flyloft-0.1.0/tests/scripts/common.py +164 -0
  83. flyloft-0.1.0/tests/scripts/crash.py +2 -0
  84. flyloft-0.1.0/tests/scripts/failing_job.py +10 -0
  85. flyloft-0.1.0/tests/scripts/happy_path.py +14 -0
  86. flyloft-0.1.0/tests/scripts/lenient_critic.py +14 -0
  87. flyloft-0.1.0/tests/scripts/missing_results.py +11 -0
  88. flyloft-0.1.0/tests/scripts/post_then_crash.py +6 -0
  89. flyloft-0.1.0/tests/scripts/retracting.py +8 -0
  90. flyloft-0.1.0/tests/scripts/silent_exit.py +2 -0
  91. flyloft-0.1.0/tests/scripts/strict_critic.py +29 -0
  92. flyloft-0.1.0/tests/unit/__init__.py +0 -0
  93. flyloft-0.1.0/tests/unit/test_actions.py +319 -0
  94. flyloft-0.1.0/tests/unit/test_assembly.py +288 -0
  95. flyloft-0.1.0/tests/unit/test_claude_code.py +89 -0
  96. flyloft-0.1.0/tests/unit/test_cli.py +66 -0
  97. flyloft-0.1.0/tests/unit/test_config.py +62 -0
  98. flyloft-0.1.0/tests/unit/test_consistency.py +174 -0
  99. flyloft-0.1.0/tests/unit/test_gh.py +55 -0
  100. flyloft-0.1.0/tests/unit/test_launch.py +166 -0
  101. flyloft-0.1.0/tests/unit/test_migrate.py +24 -0
  102. flyloft-0.1.0/tests/unit/test_opencode.py +76 -0
  103. flyloft-0.1.0/tests/unit/test_publication.py +156 -0
  104. flyloft-0.1.0/tests/unit/test_runs.py +138 -0
  105. flyloft-0.1.0/tests/unit/test_scripted.py +140 -0
  106. flyloft-0.1.0/tests/unit/test_service.py +100 -0
  107. flyloft-0.1.0/tests/unit/test_ssh.py +74 -0
  108. flyloft-0.1.0/tests/unit/test_store.py +153 -0
  109. flyloft-0.1.0/tests/unit/test_tools.py +297 -0
  110. flyloft-0.1.0/tests/unit/test_toyproject.py +36 -0
  111. flyloft-0.1.0/tests/unit/test_workspace.py +59 -0
  112. flyloft-0.1.0/uv.lock +1015 -0
@@ -0,0 +1,59 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ lint:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ with:
14
+ fetch-depth: 0 # hatch-vcs reads the version from git tags
15
+ - uses: astral-sh/setup-uv@v6
16
+ with:
17
+ enable-cache: true
18
+ - run: uv sync --locked
19
+ - run: uv run ruff format --check .
20
+ - run: uv run ruff check .
21
+
22
+ types:
23
+ runs-on: ubuntu-latest
24
+ steps:
25
+ - uses: actions/checkout@v4
26
+ with:
27
+ fetch-depth: 0
28
+ - uses: astral-sh/setup-uv@v6
29
+ with:
30
+ enable-cache: true
31
+ - run: uv sync --locked
32
+ - run: uv run ty check src
33
+
34
+ tests:
35
+ runs-on: ubuntu-latest
36
+ services:
37
+ postgres:
38
+ image: postgres:18
39
+ env:
40
+ POSTGRES_PASSWORD: flyloft
41
+ POSTGRES_DB: flyloft_test
42
+ ports:
43
+ - 5432:5432
44
+ options: >-
45
+ --health-cmd "pg_isready -U postgres"
46
+ --health-interval 5s
47
+ --health-timeout 5s
48
+ --health-retries 10
49
+ env:
50
+ FLYLOFT_TEST_DATABASE_URL: postgresql://postgres:flyloft@127.0.0.1:5432/flyloft_test
51
+ steps:
52
+ - uses: actions/checkout@v4
53
+ with:
54
+ fetch-depth: 0
55
+ - uses: astral-sh/setup-uv@v6
56
+ with:
57
+ enable-cache: true
58
+ - run: uv sync --locked
59
+ - run: uv run pytest -q
@@ -0,0 +1,19 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ jobs:
8
+ publish:
9
+ runs-on: ubuntu-latest
10
+ permissions:
11
+ id-token: write # trusted publishing: PyPI accepts this workflow's OIDC token, no API token
12
+ contents: read
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+ with:
16
+ fetch-depth: 0 # hatch-vcs reads the version from the release tag
17
+ - uses: astral-sh/setup-uv@v6
18
+ - run: uv build
19
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,6 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.pyc
4
+ .pytest_cache/
5
+ *.egg-info/
6
+ src/flyloft/_version.py
@@ -0,0 +1 @@
1
+ 3.12
@@ -0,0 +1,58 @@
1
+ # Working on flyloft
2
+
3
+ flyloft runs swarms of agents that conduct machine-learning experiments on a lab's Slurm clusters. Read `docs/VISION.md` for what it is and why, and `docs/ARCHITECTURE.md` for how it is shaped and staged. This file is about how to work on it.
4
+
5
+ ## The north star
6
+
7
+ Clear, concise code and design. No overengineering. Staged implementation. End-to-end tests that recapitulate real use.
8
+
9
+ This project can fall into a complexity spiral: agents solving imagined problems, adding generality nobody asked for, and building for stages that do not exist yet. Every rule below exists to stop that. When a rule and your instinct disagree, follow the rule and tell the human.
10
+
11
+ ## Rules
12
+
13
+ 1. **Know the stage.** `docs/ARCHITECTURE.md` names the current stage and its end-to-end scenario. Everything you build serves that scenario.
14
+ 2. **Build only what the scenario exercises.** If a change is not reachable from the current stage's scenario, do not make it. Not as a helper, not as a hook, not as preparation.
15
+ 3. **Generalize only along the shape list.** The architecture names the few decisions that may be general at cardinality one: identities, entry types, interfaces. Everything else is concrete. No interface with one implementation. No flag with one value in use. No handling for a failure nobody has observed.
16
+ 4. **Prefer deletion.** The smaller diff wins. Removing code is progress.
17
+ 5. **Solve encountered problems.** A problem counts when it has happened in a test, a live run, or a review. "It could happen" is not encountered.
18
+ 6. **Park ideas; do not build them.** When you see something the system will need later, add one line to the deferred list in `docs/ARCHITECTURE.md` and tell the human. Never build it quietly.
19
+ 7. **Test end to end.** Every stage has a scenario test that does what a researcher would do and checks what a researcher would check. Every feature is reachable from one. Unit tests are for intricate logic, not for coverage.
20
+ 8. **Design before code.** Brainstorm, present the design, get approval, write the plan, then build. Small tasks get a short design in chat; the approval gate does not shrink with the task.
21
+
22
+ ## The smell of the spiral
23
+
24
+ Stop and reconsider when you notice any of these in your own work:
25
+
26
+ - "While I'm here" or "for future use".
27
+ - A class named Manager, Handler, Framework, Engine, or Base.
28
+ - An abstract base class or protocol with one implementation.
29
+ - A configuration option, environment variable, or flag with one value in use.
30
+ - A retry, fallback, or recovery path for a failure that has never occurred.
31
+ - A helper module that exists to be shared by code that does not exist yet.
32
+ - A design section or plan task that cannot name the scenario it serves.
33
+ - A test that exercises a branch no scenario reaches.
34
+
35
+ ## Documents
36
+
37
+ - `docs/VISION.md`: what flyloft does and why. Approved; change only with the human.
38
+ - `docs/ARCHITECTURE.md`: the spine, the subsystem boundaries, the stage plan, the shape list, the deferred list.
39
+ - `docs/design/`: one design document per stage, written when the stage begins, never earlier.
40
+ - `docs/plans/`: implementation plans, one per stage design.
41
+
42
+ This branch is a fresh design. Documents stand alone and do not describe the system relative to any earlier flyloft implementation.
43
+
44
+ ## Setup and tests
45
+
46
+ - `uv sync` installs everything. Run commands as `uv run ...`.
47
+ - `uv run pytest` runs tiers one and unit tests. It starts a PostgreSQL container with Docker unless `FLYLOFT_TEST_DATABASE_URL` is set.
48
+ - `FLYLOFT_LIVE=1 uv run pytest -m live` runs tier two against the configured cluster and test repository.
49
+ - `uv run flyloft --help` lists the CLI.
50
+ - Before committing: `uv run ruff format .`, `uv run ruff check .`, `uv run ty check src`. CI runs the same three plus the tests on every pull request.
51
+ - Releases: publish a GitHub release tagged `vX.Y.Z`. The version comes from the tag, and the publish workflow uploads to PyPI.
52
+
53
+ ## Working with the human
54
+
55
+ - One question at a time. Prefer questions with options.
56
+ - Present a design and stop for approval before writing code, however small the change.
57
+ - Report outcomes faithfully. Failing tests are reported with their output. Skipped steps are named.
58
+ - Scope changes, budget changes, and anything that touches the vision go to the human. Do not decide them.
@@ -0,0 +1,3 @@
1
+ All instructions for working in this repository are in AGENTS.md. Read and follow it.
2
+
3
+ @AGENTS.md
flyloft-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,10 @@
1
+ Metadata-Version: 2.5
2
+ Name: flyloft
3
+ Version: 0.1.0
4
+ Summary: Agent swarms for empirical research on Slurm clusters
5
+ Requires-Python: >=3.12
6
+ Requires-Dist: jinja2>=3.1
7
+ Requires-Dist: mcp>=2
8
+ Requires-Dist: psycopg[binary]>=3.2
9
+ Requires-Dist: pydantic>=2.7
10
+ Requires-Dist: typer>=0.12
@@ -0,0 +1,265 @@
1
+ # flyloft Architecture
2
+
3
+ Status: approved 2026-09-21. Stage 1 complete on 2026-09-23 (see [design/stage-1.md](design/stage-1.md)); stage 2 complete on 2026-09-23 (tiers one, two, and three passed; see [design/stage-2.md](design/stage-2.md)). Current stage: 3, not yet designed.
4
+
5
+ This document settles the shape of flyloft: the record that holds its state, the lifecycle and authority rules, the agent turn, the subsystem boundaries, and the stages by which the system grows. It fixes what is expensive to change later and leaves everything else to the design document of the stage that needs it. [VISION.md](VISION.md) says what flyloft is for; [AGENTS.md](../AGENTS.md) says how to work on it. This document is the bridge between them.
6
+
7
+ ## 1. Context and constraints
8
+
9
+ **What flyloft is.** A research service operated by one lab. It runs agents that turn scientific questions into experiments on the lab's Slurm clusters, holds the record of what was tried and learned, and returns evidence researchers can inspect and steer. The full account is in the vision. The smallest campaign uses one implementing agent; the largest is a swarm of tens.
10
+
11
+ **Where things run.**
12
+
13
+ | Component | Where |
14
+ |---|---|
15
+ | Service, PostgreSQL, tool surface, dashboard | The lab-operated flyloft host |
16
+ | Agent processes, coding harnesses, worktrees | The flyloft host, as local processes |
17
+ | Experiments | Any Slurm cluster the lab can reach, local or cloud-hosted |
18
+ | Model inference | Wherever a campaign's configuration points: an institutional endpoint or a hosted provider |
19
+
20
+ flyloft hosts the agents. This rules out vendor-hosted agent runtimes, in which a provider runs the loop and a sandbox on its own servers, for three reasons. The record must be the only state, and a hosted session store would be a second one. Every action must pass through granted tools, and a vendor sandbox's built-in tools would not. Code and data stay under lab control, with only prompt content leaving the host. Model choice is unconstrained by this boundary, and the per-campaign rule holds: a campaign configured for institutional inference never falls back to a hosted model.
21
+
22
+ **Standards.** There is no standard for the shared-record layer, and no lab has published one; each has rolled its own. MCP is the standard for how agents call tools, and flyloft's tool surface is an MCP server. The record's schema is flyloft's own. Everything else in the coordination layer is chosen on merit.
23
+
24
+ **Lineage.** The hypothesis board is a blackboard in the sense of Hearsay-II: roles are knowledge sources posting to a shared structure, and the orchestrator with its attention queue is BB1's control blackboard. Two 2026 papers on governed shared memory name provenance collapse, stale propagation, and lost dissent as the failure modes of agent coordination layers. Those are the failure modes the vision's currency, contradiction, and reconstructability requirements exist to prevent. References are at the end.
25
+
26
+ **Stack.** Python 3.12, uv, pydantic v2, typer, psycopg, the official MCP Python SDK, and PostgreSQL with numbered SQL migrations.
27
+
28
+ ## 2. The record
29
+
30
+ All state lives in PostgreSQL in four kinds of table. All identities are UUIDs.
31
+
32
+ **Entries** are append-only. Every communication and every state change is an entry with a campaign, a kind, an author, a time, a mandatory summary, and a typed body validated against the schema for its kind. The author is an agent or a human. Nothing is updated or deleted.
33
+
34
+ **Links** connect an entry to another entry or to an entity, with a named relation such as tests, refutes, reviews, or supersedes. Provenance is a walk over links: a finding's chain to its hypothesis, runs, commits, and artifacts is this table.
35
+
36
+ **Entities with state** are campaign, agent, hypothesis, and run. Each carries a current-state column. State changes only by posting an entry, and the column is updated in the same transaction. The entry is the truth; the column is a convenience that must equal what the entries imply, and a test enforces that equality. A finding is also an entity, created by a finding entry; at stage one it has nothing to transition and carries no state column, and stage two adds one.
37
+
38
+ At least these columns:
39
+
40
+ - *campaign*: project, question, success criterion, baseline, constraints, submission cap, state.
41
+ - *agent*: campaign, role, substrate, workspace path, state, last turn.
42
+ - *hypothesis*: campaign, state, the proposal entry.
43
+ - *run*: hypothesis, tested commit, cluster, job identity, application state, raw scheduler state, last observed, run directory.
44
+ - *finding*: campaign, hypothesis, the finding entry.
45
+
46
+ **Turns** record one row per agent turn: agent, role, substrate, the path of the rendered assignment on disk, the entry identities assembled into its context, exit status, duration, and tokens and cost where the substrate reports them. Entries carry the turn that produced them. This is how any decision can be reconstructed: what the agent was given is on disk, which entries it saw is in the row, and what it produced is a query.
47
+
48
+ **Entry kinds at stage one.** Seven.
49
+
50
+ | Kind | Author | Effect |
51
+ |---|---|---|
52
+ | proposal | agent | Creates a hypothesis |
53
+ | run_request | agent | Creates a run against a hypothesis and a commit |
54
+ | result | system | Evidence or failure diagnostics for a finished run |
55
+ | assessment | agent | Reads a result that did not support the hypothesis, moves its state, states the next step |
56
+ | finding | agent | A supported claim with provenance; creates a finding and moves the hypothesis to supported |
57
+ | question | agent | Asks a human |
58
+ | steering | human | An instruction or decision for the campaign |
59
+
60
+ An assessment must link to the run whose result it judges. It exists so an agent has a typed place to say what a result means and what it will try next, without a free-form note kind that would drift toward chat. Later roles add kinds such as critique, digest, and decision. The tables do not change.
61
+
62
+ **Two kinds of state.** The record holds decisions and evidence. Observed external state, meaning Slurm job status and pull request status, lives on the entity row with a last-observed timestamp. Posting an entry per queue poll would drown the record; the system posts one result entry when a run reaches a terminal state. The timestamp satisfies the vision's currency requirement with one column.
63
+
64
+ ## 3. Lifecycle and authority
65
+
66
+ **Hypothesis lifecycle at stage one.**
67
+
68
+ | State | Entered by |
69
+ |---|---|
70
+ | proposed | a proposal entry |
71
+ | testing | a run_request entry |
72
+ | supported | a finding entry |
73
+ | refuted, inconclusive, unexecuted | an assessment |
74
+ | abandoned | the campaign stopping or concluding with the hypothesis open |
75
+
76
+ Stage two changes the last step: a finding entry leaves the hypothesis in testing, and a critique that accepts the finding moves it to supported. Later stages insert screened and admitted between proposed and testing, and replicated before review. States are text and transitions are code, so this growth is not shape.
77
+
78
+ **Run lifecycle.** preparing, queued, running, finished, failed, canceled, unknown. The application state is derived from observed scheduler state. A job missing from the queue is unknown until confirmed, never assumed finished. A finished job without its expected result files yields an incomplete result, not a conclusion.
79
+
80
+ **Campaign lifecycle.** active, blocked, stopped, done. Blocked means a human is needed: an agent asked a question or a turn failed. Stopped means a human stopped new work; outstanding runs finish and are collected.
81
+
82
+ **Agent state.** idle, active, blocked. The active state is the claim that prevents a second concurrent turn for the same agent. It lives on the agent, not the campaign.
83
+
84
+ **Authority** is a mapping from role to entry kinds, enforced at one place: the tool surface. An agent's tool server is launched for its turn with its role's tool list, so an implementer cannot post steering because no such tool exists in its session.
85
+
86
+ | Role | May post |
87
+ |---|---|
88
+ | implementer | proposal, run_request, assessment, finding, question |
89
+ | critic | critique |
90
+ | system | result |
91
+ | human | steering; and campaign creation, stop, and run cancellation through the CLI |
92
+
93
+ The question, success criterion, and baseline are campaign columns written at creation, with no tool that changes them. An agent that disagrees with them posts a question. This is the whole enforcement of the vision's most important guard.
94
+
95
+ ## 4. The turn
96
+
97
+ A turn is one disposable process. The system assembles context, launches the agent through its substrate, the agent acts through tools, the process exits, and the system records what happened. No agent process exists while the cluster is working.
98
+
99
+ **Wake rule.** An agent gets a turn when its campaign is active, it is idle, and an entry it did not author has appeared since its last turn. A turn counts toward the wake rule when it posts an entry, whatever its exit status; a turn that posted nothing leaves what it saw still new to the agent. At stage one that means campaign start, a result, or steering. The same rule later serves inboxes and digests, since those are entries.
100
+
101
+ **Assembly** is a function of role, agent, and campaign that produces one rendered assignment, written to disk and referenced from the turn row. At stage one it renders the campaign's question, criterion, baseline, and constraints; the project's research brief from its repository; the submissions remaining; and the whole record so far: hypotheses with states, runs with results, findings, assessments, and steering. A single campaign's record fits in a turn's context, so there is no read tool. Run directories are on the same host, so the assignment names them and the agent reads logs from disk.
102
+
103
+ **Tools.** The implementer's MCP server exposes five tools, one per entry kind it may post: propose, request_run, assess, post_finding, ask. Each validates its body against the kind's schema and returns errors in the turn, so the agent corrects mistakes before it exits. Two rules live in request_run: the worktree must be clean and HEAD is recorded as the tested commit; and one run may be outstanding per campaign. The tool records intent and returns a run identity. Staging and submission happen after the turn, and the agent learns the outcome from the result entry in its next turn. SSH latency and submission failures never enter the agent's process.
104
+
105
+ **Substrates.** A substrate executes a turn: run this assignment with these tools and report exit status. Two exist at stage one, which is what justifies the interface. The coding-harness substrate launches a harness headless in the worktree with the assignment as its prompt and the tool server configured. The scripted substrate, used in tests, connects to the same tool server and issues tool calls from a script. Every role runs in a coding harness or the scripted substrate; a role that does not edit code runs under a read-only tool policy. A native tool-calling loop is deferred until a role's harness overhead or a subscription's limits are observed to be a problem.
106
+
107
+ **Workspace.** One git worktree per agent off the campaign branch. The agent commits its own changes. Publication pushes the branch and opens or updates the pull request when a critic accepts a finding.
108
+
109
+ **Ending.** A turn ends when the process exits. A turn that exits without posting anything blocks the campaign with diagnostics. There is no conclude action: when the agent believes the campaign is finished, it asks, and the human's steering stops the campaign. The end decision stays with a person until the orchestrator exists at stage five.
110
+
111
+ ## 5. Subsystems and process shape
112
+
113
+ One Python application. Subsystems are modules with owned responsibilities, not services.
114
+
115
+ | Module | Owns | Stage |
116
+ |---|---|---|
117
+ | record | The four kinds of table, entry schemas, links, state transitions. The only code that touches PostgreSQL. | 1 |
118
+ | tools | The MCP server: per-role tool lists, validation, calls into record. | 1 |
119
+ | turns | The wake rule, assembly, substrates, turn rows. | 1 |
120
+ | workspace | Clone and worktree per agent, clean-tree check, branch push. | 1 |
121
+ | runs | Staging, Slurm submission, observation, collection, result entries. | 1 |
122
+ | publication | Pull request create and update from findings. | 1 |
123
+ | cli | Register a project, start a campaign, status, steer, stop, cancel. | 1 |
124
+ | config | Projects, clusters, harnesses, credentials. | 1 |
125
+ | service | The tick: reap, wake, submit, observe, cancel, publish; startup reconciliation. | 1 |
126
+ | web | Read API and dashboard over record. | 3 |
127
+ | budget | Ledger and admission beyond a submission cap. | 3 |
128
+ | evaluation | Replication policy, promotion. | 4, 6 |
129
+ | knowledge | Proximity, cross-campaign memory. | 4 |
130
+ | direction | Islands, orchestrator, librarians, decisions. | 5 |
131
+ | recovery | Reconciliation after interruption. | When a stage loses work |
132
+
133
+ **Dependency rules.** Only record writes to the database. Only runs calls the cluster. Only workspace and publication call git and GitHub. No agent path reaches any of them directly.
134
+
135
+ **Process shape at stage one.** Three kinds of process share PostgreSQL through the record module.
136
+
137
+ ```mermaid
138
+ flowchart LR
139
+ R[Researcher] --> CLI
140
+ CLI --> PG[(PostgreSQL)]
141
+ S[Service] <--> PG
142
+ S -->|launch turn| H[Harness]
143
+ H -->|stdio| T[Tool server]
144
+ T --> PG
145
+ H --> M[Model endpoint]
146
+ S -->|ssh| C[Slurm clusters]
147
+ S --> G[GitHub]
148
+ ```
149
+
150
+ - **The service.** One long-running process whose tick wakes agents, launches turns, stages and submits runs, observes jobs, and publishes.
151
+ - **Per-turn processes.** The harness, and the tool server it launches as a stdio subprocess with the agent and turn identity in its environment. The server writes entries through record.
152
+ - **The CLI.** Short-lived. It reads the record for status and posts steering entries.
153
+
154
+ Two processes write to the record from the first turn, the service and the tool server, so PostgreSQL is a stage-one necessity rather than preparation.
155
+
156
+ **The record is the control channel.** Human commands are steering entries. Stop transitions the campaign in the same transaction. Cancel marks the run, and the service issues the cancellation on its next observation. There is no control socket, and every intervention is in the record by construction.
157
+
158
+ ## 6. How the system grows
159
+
160
+ **Shape versus features.** The system can fail in two ways: from identities baked in wrong early, and from complexity nobody needed. Both are avoided by one rule. The *shape* of the system, its identities and interfaces, is right from the start even at cardinality one. *Features* are built only when a stage's scenario requires them. The following is the closed list of what may be general at cardinality one. Everything not on it is concrete.
161
+
162
+ 1. An agent is an entity distinct from its campaign, and has a role.
163
+ 2. All inter-turn state is typed entries; entity state columns are derived.
164
+ 3. Provenance is a links table.
165
+ 4. Every turn records the context it was given.
166
+ 5. UUID identities.
167
+ 6. Agents act only through the tool surface, with tool lists per role.
168
+ 7. A substrate interface, justified by two implementations.
169
+ 8. A worktree per agent.
170
+ 9. PostgreSQL with numbered SQL migrations.
171
+ 10. Question, criterion, and baseline are immutable campaign columns.
172
+ 11. Observed external state lives on entity rows with a last-observed timestamp.
173
+ 12. Human actions are entries; the record is the control channel.
174
+
175
+ **Stages.** Each stage is a system a researcher could use for real work. Its end-to-end scenario is its only acceptance. Each stage gets one design document in `docs/design/`, written when the stage begins, and one plan in `docs/plans/`.
176
+
177
+ **Stage 1: one implementer.** A researcher registers the toy project and starts a campaign with a question, criterion, baseline, and submission cap. The agent establishes the baseline, proposes a change, runs it, assesses the result, posts a finding, and a pull request appears carrying the finding and its provenance. The researcher posts an instruction and the next turn honors it. The researcher stops the campaign and the open hypothesis is abandoned. Failure paths in scope: a failed job yields unexecuted, an agent that exits without posting blocks the campaign, missing metrics yield an incomplete result.
178
+
179
+ **Stage 2: a critic.** A finding is a candidate until a critic, running in a harness under a read-only tool policy, accepts it or blocks it with a demand for more runs. Only accepted findings reach the pull request. Adds the critique kind, finding state, per-role harness and model selection, and role wake filters. First communication between roles through the record.
180
+
181
+ **Stage 3: several implementers.** Hypotheses in flight in parallel, each in its own worktree, admitted up to a concurrency limit. An integrator merges accepted implementations and keeps the branch runnable. A read-only web view shows the board. The same question runs with one and several implementers, reporting verified improvement and cost: the first control arm. Adds the budget ledger and moves the tool server to HTTP with per-turn tokens.
182
+
183
+ **Stage 4: a population.** Generators with distinct framings propose. Proximity merges duplicates and flags repeats from earlier campaigns. A coordinator admits under a posture and orders replication. Adds the decision kind, the screened and admitted states, replication policy, and the knowledge module.
184
+
185
+ **Stage 5: islands.** The orchestrator proposes a decomposition and the researcher approves it. Two islands run opposing bets on their own branches. Librarians write digests that preserve disagreement. The orchestrator shifts budget and may conclude a bounded campaign. The dashboard gains steering and intervention, the attention queue, and notifications. The reporter writes the account.
186
+
187
+ **Stage 6: indefinite campaigns.** A direction criterion and a node envelope. Promotion by policy, reports on a cadence, re-decomposition when dry, ended only by a person.
188
+
189
+ Recovery is built when a stage loses work, most likely stage three, when a service restart first interrupts many turns at once. It is not scheduled.
190
+
191
+ **Known stage-one limitation.** The tool server is a stdio subprocess launched by the harness, so database credentials are in the harness's environment and a misbehaving agent could bypass the tool surface. With one trusted agent on a lab host this is accepted. Stage three closes it, when agents become mutually untrusted.
192
+
193
+ ## 7. Testing
194
+
195
+ Every stage's scenario is exercised end to end, doing what a researcher would do and checking what a researcher would check: the record, the runs, the pull request. Unit tests cover intricate logic, not coverage.
196
+
197
+ **Fixtures.**
198
+
199
+ - A toy research project: a small model that trains in seconds on CPU, a real metric, and a configuration knob that is an obvious improvement. Campaigns in tests run against it.
200
+ - The scripted substrate: an agent whose behavior is a deterministic script, issuing real tool calls to the real tool server.
201
+ - A test repository on GitHub for publication.
202
+
203
+ **Tiers.**
204
+
205
+ 1. Fast end-to-end on every change, with fakes at the Slurm and GitHub boundaries and the scripted substrate. The real record, tool server, workspace, and scheduler.
206
+ 2. Real-infrastructure end-to-end on demand, against the local Slinky cluster and the test repository, still with the scripted substrate.
207
+ 3. Live acceptance by hand at the close of each stage, with a real model on the harness substrate.
208
+
209
+ The architecture is demonstrated when stage one's scenario passes all three tiers.
210
+
211
+ ## 8. Deferred
212
+
213
+ Ideas that have arrived before their stage. Adding a line here is how an impulse to build is discharged. Nothing on this list is built until its stage begins or the encountered problem it names occurs.
214
+
215
+ | Item | Picked up |
216
+ |---|---|
217
+ | Native tool-calling substrate | When a role's harness overhead or a subscription's limits are observed to be a problem |
218
+ | Campaign-wide default harness and model with per-role overrides | When the role count makes per-role listing tedious |
219
+ | A critic that asks the researchers directly; today the implementer is the only role that talks to humans | When a critic's block cannot express what it needs |
220
+ | Several critics with different framings per candidate finding | When one critic's acceptance proves a rubber stamp, expected by stage 4 |
221
+ | Integrator role; run admission with per-campaign slots | Stage 3 |
222
+ | Budget ledger for tokens, GPU-hours, and cost; a budget query tool | Stage 3 |
223
+ | HTTP tool server with per-turn tokens | Stage 3 |
224
+ | Read-only web API and dashboard; AG-UI as the streaming format, decided in that design | Stage 3 |
225
+ | Control-arm configuration and reporting | Stage 3 |
226
+ | Read and search tools; per-role context filtering | When a campaign's record no longer fits in a turn's context, expected at stage 3 |
227
+ | Decision kind; screened and admitted states; coordinator role; risk posture | Stage 4 |
228
+ | Replication policy | Stage 4 |
229
+ | Proximity role; knowledge module; cross-campaign memory; full-text search | Stage 4 |
230
+ | Island entity and island column on entries; digest kind; librarian, orchestrator, and reporter roles | Stage 5 |
231
+ | Addressing an entry to an agent's inbox | Stage 5 |
232
+ | Campaign feed and human-visibility flag; attention queue; notifications; dashboard interventions | Stage 5 |
233
+ | Conclude as an orchestrator decision | Stage 5 |
234
+ | Promotion policy; footprint budgets; cadence reporting; pre-authorized posture changes | Stage 6 |
235
+ | Recovery and reconciliation; durable-execution libraries evaluated then | When a stage loses work |
236
+ | Role registry and prompt versioning | When a second campaign must be reproducible under changed prompts |
237
+ | Artifact fetch tool | When an agent needs an artifact not on the host |
238
+ | Provider rate limiting | When a provider limit is hit |
239
+ | Remote executors; a second host; worker processes; a message broker | When one host saturates |
240
+ | Embedding index | When full-text search proves insufficient for proximity |
241
+ | OpenTelemetry GenAI span names | When tracing is added |
242
+ | A2A-compatible task states | If an external agent ever needs a bridge |
243
+ | Artifact retention and garbage collection; W&B synchronization | When storage fills or a project asks |
244
+ | Cost estimate on proposals | Stage 4 |
245
+ | Harness environment scrubbed of git and cluster credentials | Stage 3, with per-turn credentials |
246
+ | Observation of pull request state | When something reads it |
247
+ | Timeout for runs in unknown | When one is encountered |
248
+ | Projects registry in the repository | Not planned; the configuration directory serves |
249
+ | Metric formatting in the pull request body and `campaign show` (dict text today); Limitations line joins the Commits line | Stage 3, with the web view |
250
+ | Turn duration is quantized to the tick interval (end time stamped at reap) | When anyone needs finer timing |
251
+ | Per-project container image override (cluster-level image today) | When a project needs its own image |
252
+ | A stage-one campaign that runs out of submissions has no explicit end; the agent asks and a human stops it | Stage 5, with decisions |
253
+ | Session resume for harness substrates | Not planned; fresh turns are the design |
254
+ | Frozen retrieval corpus | Not planned |
255
+ | A critic launched with no candidate finding (a same-turn withdrawal, or two candidates at once) | When a harness implementer produces it |
256
+ | Finding state when the campaign stops | When a stopped campaign's pull request misleads a reader |
257
+
258
+ ## References
259
+
260
+ - Erman, Hayes-Roth, Lesser, and Reddy. "The Hearsay-II Speech-Understanding System." *Computing Surveys* 12(2), 1980.
261
+ - B. Hayes-Roth. "A Blackboard Architecture for Control." *Artificial Intelligence* 26, 1985.
262
+ - Nii. "Blackboard Systems," parts one and two. *AI Magazine* 7, 1986.
263
+ - "Governed Shared Memory." arXiv 2606.24535, June 2026.
264
+ - "Governance Gaps." arXiv 2606.31498, June 2026.
265
+ - Model Context Protocol specification, 2026-07-28. https://modelcontextprotocol.io/specification/2026-07-28