loopcaptain 0.3.1__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 (148) hide show
  1. loopcaptain-0.3.1/.agent/AGENT_IMPROVEMENTS.md +11 -0
  2. loopcaptain-0.3.1/.agent/DEFERRED_VERIFICATION.md +13 -0
  3. loopcaptain-0.3.1/.agent/FINDINGS.md +17 -0
  4. loopcaptain-0.3.1/.agent/UPSTREAM_ISSUES.md +8 -0
  5. loopcaptain-0.3.1/.editorconfig +23 -0
  6. loopcaptain-0.3.1/.env.example +16 -0
  7. loopcaptain-0.3.1/.github/CODEOWNERS +2 -0
  8. loopcaptain-0.3.1/.github/ISSUE_TEMPLATE/bug.yml +26 -0
  9. loopcaptain-0.3.1/.github/ISSUE_TEMPLATE/config.yml +5 -0
  10. loopcaptain-0.3.1/.github/ISSUE_TEMPLATE/loop-not-showing.yml +23 -0
  11. loopcaptain-0.3.1/.github/ISSUE_TEMPLATE/protocol.yml +25 -0
  12. loopcaptain-0.3.1/.github/dependabot.yml +32 -0
  13. loopcaptain-0.3.1/.github/pull_request_template.md +19 -0
  14. loopcaptain-0.3.1/.github/workflows/ci.yml +69 -0
  15. loopcaptain-0.3.1/.github/workflows/docs.yml +57 -0
  16. loopcaptain-0.3.1/.github/workflows/links.yml +28 -0
  17. loopcaptain-0.3.1/.github/workflows/pr-description.yml +62 -0
  18. loopcaptain-0.3.1/.github/workflows/release.yml +108 -0
  19. loopcaptain-0.3.1/.github/workflows/scorecard.yml +35 -0
  20. loopcaptain-0.3.1/.github/workflows/security.yml +37 -0
  21. loopcaptain-0.3.1/.gitignore +23 -0
  22. loopcaptain-0.3.1/.pre-commit-config.yaml +130 -0
  23. loopcaptain-0.3.1/.python-version +1 -0
  24. loopcaptain-0.3.1/AGENTS.md +276 -0
  25. loopcaptain-0.3.1/CHANGELOG.md +103 -0
  26. loopcaptain-0.3.1/CITATION.cff +17 -0
  27. loopcaptain-0.3.1/CLAUDE.md +1 -0
  28. loopcaptain-0.3.1/CODE_OF_CONDUCT.md +10 -0
  29. loopcaptain-0.3.1/CONTRIBUTING.md +26 -0
  30. loopcaptain-0.3.1/GEMINI.md +1 -0
  31. loopcaptain-0.3.1/LICENSE +202 -0
  32. loopcaptain-0.3.1/Makefile +49 -0
  33. loopcaptain-0.3.1/NOTICE +11 -0
  34. loopcaptain-0.3.1/PKG-INFO +220 -0
  35. loopcaptain-0.3.1/README.md +185 -0
  36. loopcaptain-0.3.1/SECURITY.md +31 -0
  37. loopcaptain-0.3.1/docs/ARCHITECTURE.md +448 -0
  38. loopcaptain-0.3.1/docs/agents.md +200 -0
  39. loopcaptain-0.3.1/docs/assets/banner-dark.svg +19 -0
  40. loopcaptain-0.3.1/docs/assets/banner-light.svg +19 -0
  41. loopcaptain-0.3.1/docs/assets/hero-dark.svg +65 -0
  42. loopcaptain-0.3.1/docs/assets/hero-light.svg +65 -0
  43. loopcaptain-0.3.1/docs/assets/parts-dark.svg +49 -0
  44. loopcaptain-0.3.1/docs/assets/parts-light.svg +49 -0
  45. loopcaptain-0.3.1/docs/assets/request-dark.svg +62 -0
  46. loopcaptain-0.3.1/docs/assets/request-light.svg +62 -0
  47. loopcaptain-0.3.1/docs/assets/screenshot-connect-dark.webp +0 -0
  48. loopcaptain-0.3.1/docs/assets/screenshot-connect.webp +0 -0
  49. loopcaptain-0.3.1/docs/assets/screenshot-findings-dark.webp +0 -0
  50. loopcaptain-0.3.1/docs/assets/screenshot-findings.webp +0 -0
  51. loopcaptain-0.3.1/docs/assets/screenshot-loop-dark.webp +0 -0
  52. loopcaptain-0.3.1/docs/assets/screenshot-loop.webp +0 -0
  53. loopcaptain-0.3.1/docs/assets/screenshot-overview-dark.webp +0 -0
  54. loopcaptain-0.3.1/docs/assets/screenshot-overview.webp +0 -0
  55. loopcaptain-0.3.1/docs/assets/screenshot-rounds-dark.webp +0 -0
  56. loopcaptain-0.3.1/docs/assets/screenshot-rounds.webp +0 -0
  57. loopcaptain-0.3.1/docs/assets/screenshot-waiting-dark.webp +0 -0
  58. loopcaptain-0.3.1/docs/assets/screenshot-waiting.webp +0 -0
  59. loopcaptain-0.3.1/docs/assets/trust-dark.svg +29 -0
  60. loopcaptain-0.3.1/docs/assets/trust-light.svg +29 -0
  61. loopcaptain-0.3.1/docs/board.md +63 -0
  62. loopcaptain-0.3.1/docs/changelog.md +1 -0
  63. loopcaptain-0.3.1/docs/connect.md +1 -0
  64. loopcaptain-0.3.1/docs/contributing.md +1 -0
  65. loopcaptain-0.3.1/docs/get-started.md +67 -0
  66. loopcaptain-0.3.1/docs/index.md +45 -0
  67. loopcaptain-0.3.1/docs/installation.md +83 -0
  68. loopcaptain-0.3.1/docs/llms.txt +27 -0
  69. loopcaptain-0.3.1/docs/opentelemetry.md +46 -0
  70. loopcaptain-0.3.1/docs/protocol/index.md +1 -0
  71. loopcaptain-0.3.1/docs/reference/cli.md +142 -0
  72. loopcaptain-0.3.1/docs/reference/configuration.md +24 -0
  73. loopcaptain-0.3.1/docs/reference/mcp.md +85 -0
  74. loopcaptain-0.3.1/docs/security.md +1 -0
  75. loopcaptain-0.3.1/docs/setup.md +88 -0
  76. loopcaptain-0.3.1/pyproject.toml +103 -0
  77. loopcaptain-0.3.1/scripts/docs_pages.py +118 -0
  78. loopcaptain-0.3.1/scripts/forbidden_words.sh +21 -0
  79. loopcaptain-0.3.1/scripts/readme_assets.py +621 -0
  80. loopcaptain-0.3.1/scripts/release.py +109 -0
  81. loopcaptain-0.3.1/scripts/shots.py +125 -0
  82. loopcaptain-0.3.1/scripts/showcase.py +318 -0
  83. loopcaptain-0.3.1/src/loopcaptain/__init__.py +0 -0
  84. loopcaptain-0.3.1/src/loopcaptain/agents.py +162 -0
  85. loopcaptain-0.3.1/src/loopcaptain/board/AGENTS.md +39 -0
  86. loopcaptain-0.3.1/src/loopcaptain/board/__init__.py +0 -0
  87. loopcaptain-0.3.1/src/loopcaptain/board/app.py +430 -0
  88. loopcaptain-0.3.1/src/loopcaptain/board/discover.py +249 -0
  89. loopcaptain-0.3.1/src/loopcaptain/board/files.py +94 -0
  90. loopcaptain-0.3.1/src/loopcaptain/board/filters.py +48 -0
  91. loopcaptain-0.3.1/src/loopcaptain/board/graph.py +139 -0
  92. loopcaptain-0.3.1/src/loopcaptain/board/overview.py +59 -0
  93. loopcaptain-0.3.1/src/loopcaptain/board/static/board.js +41 -0
  94. loopcaptain-0.3.1/src/loopcaptain/board/static/fonts/OFL.txt +94 -0
  95. loopcaptain-0.3.1/src/loopcaptain/board/static/fonts/inter-latin-wght-normal.woff2 +0 -0
  96. loopcaptain-0.3.1/src/loopcaptain/board/static/fonts/inter-tight-latin-wght-normal.woff2 +0 -0
  97. loopcaptain-0.3.1/src/loopcaptain/board/static/mark.svg +5 -0
  98. loopcaptain-0.3.1/src/loopcaptain/board/static/style.css +425 -0
  99. loopcaptain-0.3.1/src/loopcaptain/board/steer.py +207 -0
  100. loopcaptain-0.3.1/src/loopcaptain/board/templates/base.html +27 -0
  101. loopcaptain-0.3.1/src/loopcaptain/board/templates/connect.html +35 -0
  102. loopcaptain-0.3.1/src/loopcaptain/board/templates/file.html +9 -0
  103. loopcaptain-0.3.1/src/loopcaptain/board/templates/loop.html +357 -0
  104. loopcaptain-0.3.1/src/loopcaptain/board/templates/overview.html +108 -0
  105. loopcaptain-0.3.1/src/loopcaptain/board/templates/unreadable.html +17 -0
  106. loopcaptain-0.3.1/src/loopcaptain/board/trend.py +185 -0
  107. loopcaptain-0.3.1/src/loopcaptain/cli.py +204 -0
  108. loopcaptain-0.3.1/src/loopcaptain/demo/__init__.py +59 -0
  109. loopcaptain-0.3.1/src/loopcaptain/demo/loop.py +311 -0
  110. loopcaptain-0.3.1/src/loopcaptain/mcp_server/AGENTS.md +23 -0
  111. loopcaptain-0.3.1/src/loopcaptain/mcp_server/__init__.py +0 -0
  112. loopcaptain-0.3.1/src/loopcaptain/mcp_server/server.py +181 -0
  113. loopcaptain-0.3.1/src/loopcaptain/prompts.py +27 -0
  114. loopcaptain-0.3.1/src/loopcaptain/protocol/AGENTS.md +24 -0
  115. loopcaptain-0.3.1/src/loopcaptain/protocol/__init__.py +17 -0
  116. loopcaptain-0.3.1/src/loopcaptain/protocol/doctor.py +79 -0
  117. loopcaptain-0.3.1/src/loopcaptain/protocol/models.py +496 -0
  118. loopcaptain-0.3.1/src/loopcaptain/protocol/signing.py +105 -0
  119. loopcaptain-0.3.1/src/loopcaptain/protocol/state.py +595 -0
  120. loopcaptain-0.3.1/src/loopcaptain/protocol/store.py +275 -0
  121. loopcaptain-0.3.1/src/loopcaptain/protocol/validate.py +199 -0
  122. loopcaptain-0.3.1/standard/AGENTS.md +207 -0
  123. loopcaptain-0.3.1/standard/LOOPS.md +402 -0
  124. loopcaptain-0.3.1/standard/loops.py +76 -0
  125. loopcaptain-0.3.1/standard/prompts/build-a-loop.md +6 -0
  126. loopcaptain-0.3.1/standard/prompts/watch-my-loops.md +6 -0
  127. loopcaptain-0.3.1/standard/schema/event.schema.json +1096 -0
  128. loopcaptain-0.3.1/standard/schema/inbox.schema.json +93 -0
  129. loopcaptain-0.3.1/standard/schema/loop.schema.json +296 -0
  130. loopcaptain-0.3.1/standard/templates/editor-maker-lens-loop.loop.json +25 -0
  131. loopcaptain-0.3.1/standard/templates/research-loop.loop.json +27 -0
  132. loopcaptain-0.3.1/standard/templates/review-loop.loop.json +20 -0
  133. loopcaptain-0.3.1/tests/__init__.py +0 -0
  134. loopcaptain-0.3.1/tests/conftest.py +103 -0
  135. loopcaptain-0.3.1/tests/test_agents.py +66 -0
  136. loopcaptain-0.3.1/tests/test_board.py +895 -0
  137. loopcaptain-0.3.1/tests/test_demo.py +119 -0
  138. loopcaptain-0.3.1/tests/test_docs.py +90 -0
  139. loopcaptain-0.3.1/tests/test_license.py +25 -0
  140. loopcaptain-0.3.1/tests/test_mcp.py +186 -0
  141. loopcaptain-0.3.1/tests/test_messages.py +59 -0
  142. loopcaptain-0.3.1/tests/test_neutral.py +96 -0
  143. loopcaptain-0.3.1/tests/test_protocol.py +523 -0
  144. loopcaptain-0.3.1/tests/test_protocol_growth.py +234 -0
  145. loopcaptain-0.3.1/tests/test_release.py +76 -0
  146. loopcaptain-0.3.1/tests/test_showcase.py +59 -0
  147. loopcaptain-0.3.1/uv.lock +1685 -0
  148. loopcaptain-0.3.1/zensical.toml +47 -0
@@ -0,0 +1,11 @@
1
+ # Agent improvements
2
+
3
+ Evidence-backed proposals for how work in this repository is done. An
4
+ accepted one moves into its owner (AGENTS.md, a hook, a Makefile target)
5
+ and its row is deleted; the commit says where it went.
6
+
7
+ | # | Proposal | Evidence | Status |
8
+ |---|---|---|---|
9
+ | A2 | Run `ruff format` before string replacements in a file, or edit after formatting: the formatter rewraps lines and a replacement written against the old wrapping misses | Two replacement batches against `examples/demo_loop.py` failed on 2026-11-14 for this reason | open |
10
+ | A3 | Headless Chrome cannot render narrower than 500 px, so a `--window-size=390` screenshot shows a clipped desktop layout, not a phone. Check a phone with device emulation over the DevTools protocol, and make that a `make phone` target that fails when a page is wider than 390 px | The clipped 390 px shots of 2026-11-15 were the 500 px minimum; emulation found the real overflows (600eca2) | open |
11
+ | A4 | The shell here is bash 3.2: a command that mixes a heredoc with pipes or two heredocs is refused. Write a long commit message to a file and pass `-F <file>` | Several refused commits on 2026-11-15 | open |
@@ -0,0 +1,13 @@
1
+ # Deferred verification
2
+
3
+ Changes no real run has shown working yet. A row leaves when its evidence
4
+ is named in the commit that closes it.
5
+
6
+ | Commit | Change | Risk | Evidence that would close it |
7
+ |---|---|---|---|
8
+ | the MCP door | The MCP server ran over stdio against the SDK's own client (register, a round, a question, a refused call), not from a live agent session | An agent may use the tools differently than the docstrings intend, or skip calls | the server added as `loopcaptain mcp-config <agent>` prints, for two agents of different vendors, then one round reported from each session and seen on the board |
9
+ | the protocol | The example loop and one loop a fresh agent built from the first prompt (2026-11-15, validate ok, doctor passed, on the board) have written it; no real loop with real costs and long reviews has | Fields real rounds need may be missing; long summaries may crowd the round table | One round of a real loop (F1) shown on the board |
10
+ | the board | Looked at in Chrome, light and dark, at 1360 px, and at 390 px with device emulation (every page fits since 600eca2); not in Safari or Firefox, no keyboard-only pass, no automated accessibility run | Layout, focus or contrast faults outside Chrome | The pages opened in Safari and Firefox; a keyboard-only pass; a Lighthouse accessibility run |
11
+ | ci/hardening | The workflows (three Pythons and macOS, security, scorecard, links, release, docs) passed zizmor, actionlint and the workflow schema; none has run on GitHub | A runner difference, a missing environment or a Pages setting fails the first run | The branch pushed, CI green on a pull request; the docs workflow publishing once |
12
+ | the agents page | `docs/agents.md` follows a table read from each agent's documentation in the review of 2026-11-15; the entries for Gemini CLI, Cursor, opencode and Amp could not be confirmed in that review, and no line has been run | A wrong flag or file name sends a stranger the wrong way | The first prompt run in two agents of different vendors, each ending with a loop on the board |
13
+ | fix/board-message-codes | A loop page shows the green or red message only with a seal from this server process; tested through the app, not looked at on a running board | A message after a reply, stop or start no longer shows | `make demo`, `make serve`, press Stop on the example loop: the message shows; the same address with `note=` changed shows none |
@@ -0,0 +1,17 @@
1
+ # Findings
2
+
3
+ Observations outside the task at hand. A row is recorded, a person
4
+ decides; a picked row moves to `docs/BACKLOG.md`, a settled one is deleted
5
+ in the commit that settles it. Proposed: now, later, check, drop.
6
+
7
+ | # | Finding | Status | Proposed | Evidence |
8
+ |---|---|---|---|---|
9
+ | F1 | The five loops this was designed around do not write `.loops/` yet. Each maps cleanly: (a) the two brief-and-lenses loops: BRIEF front matter -> `loop.json` (lenses -> review steps, `rounds` -> `round_cap`), `.rounds/N/plan.md` -> `round_started`, `lens-*.md` with its MATERIAL GAPS count -> `step_finished` findings, `cost.md` rows -> `cost`, `gate.md`, `verdict.md` and the thread entry -> `round_finished`, "Needs you" -> `question`, the thread's reply slot -> inbox; (b) the three research loops: `brief/brief.md` -> brief, `policy.py` reviewers and models -> steps, `work/rounds/N-worklist.yaml` -> `round_started`, `N-review-*.md` -> findings, `N-gate.md`, `N-verdict.md` -> `round_finished`, the approval file -> `question` | open | now, one loop first | the loops' round folders, read 2026-11-14 |
10
+ | F2 | The repository's home and name are not final. The connect sentence in `README.md` and `standard/AGENTS.md` says "the Loopcaptain repository" without an address, and the README has no CI badge | open | now, once the home is decided | README.md, standard/AGENTS.md |
11
+ | F5 | Protocol proposal: an optional `fingerprint` per finding (the helper computes it) so the board can follow one finding across rounds and show "open since round N"; replaces the rule that reviews are fresh each round where a loop sends it | open | later: the first thing after real loops are connected | published practice on review and gates, read 2026-11-14; docs/ARCHITECTURE.md section 7 |
12
+ | F7 | Protocol proposals to leave out of version 1: control role separate from activity, four severities with a separate blocking flag, lens criteria with ids, the gate declared as data, `done_when` checks, run ids, trigger, autonomy levels, SARIF export, a `by` on a closed finding. Each adds vocabulary a loop author must learn; unknown fields are allowed, so a loop can already carry them | open | drop for version 1, revisit with F5 | published loop tooling, read 2026-11-14; docs/ARCHITECTURE.md section 14 |
13
+ | F9 | A notification bridge (a phone message when a loop asks, a reply that lands in `inbox.jsonl`) would need an outside service and a rule for who may write the inbox | open | later | published human-in-the-loop tooling, read 2026-11-14 |
14
+ | F15 | An optional exporter that tails `events.jsonl` and sends OTLP along docs/opentelemetry.md: its own process and dependency group, off by default, the first thing that would send data off the machine | open | later: when someone wants loops beside agent traces | docs/ARCHITECTURE.md section 15 |
15
+ | F17 | The tab title counts what waits; the favicon does not. A count in the icon needs a drawn image per number or script | open | later | src/loopcaptain/board/templates/overview.html |
16
+ | F18 | A prompt to build a loop, given to a fresh agent, ended with a 302-line loop file for a three-step job; a smaller starting template (one loop.py of 60 lines beside the helper) would make the first loop cheaper | open | later: check with the next acceptance run | the acceptance run of 2026-11-15 |
17
+ | F19 | "Door" is used for the two ways a loop writes its files (the MCP server's docstring and so `docs/reference/mcp.md`, `mcp_server/AGENTS.md`, `standard/LOOPS.md` section 5). A reader new to the project does not know what a door is; the architecture heading and picture now say "ways in" and name the script and MCP | open | later: reword with the next change to those files, then `make docs-pages` | `git grep -n -i door`, 2026-11-15 |
@@ -0,0 +1,8 @@
1
+ # Upstream issues
2
+
3
+ Behaviour of external tools this repository works around. Filing upstream
4
+ is a public post and needs the person's yes.
5
+
6
+ | # | Upstream | Behaviour | Workaround | Report |
7
+ |---|---|---|---|---|
8
+ | U1 | `mcp` Python SDK 2.x | `FastMCP` was renamed to `MCPServer` (`mcp.server.mcpserver`); code written for 1.x fails on import | `pyproject.toml` requires `mcp>=2.0` and `mcp_server/server.py` imports `MCPServer` | none needed: documented in the SDK's migration guide |
@@ -0,0 +1,23 @@
1
+ root = true
2
+
3
+ [*]
4
+ charset = utf-8
5
+ end_of_line = lf
6
+ insert_final_newline = true
7
+ trim_trailing_whitespace = true
8
+
9
+ [*.py]
10
+ indent_style = space
11
+ indent_size = 4
12
+
13
+ [*.{yml,yaml,json,toml}]
14
+ indent_style = space
15
+ indent_size = 2
16
+
17
+ [Makefile]
18
+ indent_style = tab
19
+
20
+ [*.{md,tex}]
21
+ indent_style = space
22
+ indent_size = 2
23
+ trim_trailing_whitespace = false
@@ -0,0 +1,16 @@
1
+ # Both are optional. The board runs without a .env file.
2
+
3
+ # Path of the config that lists the watched repositories.
4
+ # Default: loopcaptain.local.yaml in the folder the board is started in.
5
+ LOOPCAPTAIN_CONFIG=
6
+
7
+ # Folder for the pid records and logs of loops the board started.
8
+ # Default: .state in the folder the board is started in.
9
+ LOOPCAPTAIN_STATE=
10
+
11
+ # Folders, separated by ":", under which the MCP tools may write besides the
12
+ # folder the server was started in.
13
+ LOOPCAPTAIN_MCP_ROOTS=
14
+
15
+ # The key the board signs inbox lines with. Default: ~/.config/loopcaptain/inbox.key
16
+ LOOPCAPTAIN_INBOX_KEY=
@@ -0,0 +1,2 @@
1
+ # Every change is reviewed by the maintainer.
2
+ * @danielvogler
@@ -0,0 +1,26 @@
1
+ name: Something is wrong
2
+ description: The board, the command line or the MCP server does not do what it should.
3
+ labels: [bug]
4
+ body:
5
+ - type: textarea
6
+ id: what
7
+ attributes:
8
+ label: What happened, and what you expected
9
+ validations:
10
+ required: true
11
+ - type: textarea
12
+ id: steps
13
+ attributes:
14
+ label: How to see it
15
+ description: The commands, the page, and a few lines of the loop's files if they matter. Invent the content; never paste a real loop's text.
16
+ validations:
17
+ required: true
18
+ - type: input
19
+ id: version
20
+ attributes:
21
+ label: Version
22
+ description: "`loopcaptain --version` or the commit"
23
+ - type: input
24
+ id: system
25
+ attributes:
26
+ label: System and browser
@@ -0,0 +1,5 @@
1
+ blank_issues_enabled: false
2
+ contact_links:
3
+ - name: A security problem
4
+ url: https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability
5
+ about: Report it privately through this repository's security advisories, not as an issue.
@@ -0,0 +1,23 @@
1
+ name: A loop does not show up
2
+ description: A loop writes .loops/ but the board does not show it, or shows it wrong.
3
+ labels: [connect]
4
+ body:
5
+ - type: textarea
6
+ id: validate
7
+ attributes:
8
+ label: What `loopcaptain validate <repository>` prints
9
+ render: text
10
+ validations:
11
+ required: true
12
+ - type: textarea
13
+ id: config
14
+ attributes:
15
+ label: How the board watches the repository
16
+ description: The roots and repos entries, with your own folder names replaced.
17
+ render: yaml
18
+ - type: textarea
19
+ id: what
20
+ attributes:
21
+ label: What you expected to see
22
+ validations:
23
+ required: true
@@ -0,0 +1,25 @@
1
+ name: A protocol proposal
2
+ description: A field or an event the Loops protocol lacks.
3
+ labels: [protocol]
4
+ body:
5
+ - type: textarea
6
+ id: need
7
+ attributes:
8
+ label: What a real loop could not say
9
+ description: The loop, in invented words, and what it had to do instead.
10
+ validations:
11
+ required: true
12
+ - type: textarea
13
+ id: proposal
14
+ attributes:
15
+ label: The field or event, as a line of events.jsonl
16
+ render: json
17
+ validations:
18
+ required: true
19
+ - type: checkboxes
20
+ id: grows
21
+ attributes:
22
+ label: Protocol 1 only grows
23
+ options:
24
+ - label: The proposal is optional; a loop written against today's text still validates.
25
+ required: true
@@ -0,0 +1,32 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: uv
4
+ directory: /
5
+ schedule:
6
+ interval: monthly
7
+ cooldown:
8
+ default-days: 7
9
+ semver-major-days: 14
10
+ groups:
11
+ python:
12
+ patterns: ["*"]
13
+ - package-ecosystem: github-actions
14
+ directory: /
15
+ schedule:
16
+ interval: monthly
17
+ cooldown:
18
+ default-days: 7
19
+ semver-major-days: 14
20
+ groups:
21
+ actions:
22
+ patterns: ["*"]
23
+ - package-ecosystem: pre-commit
24
+ directory: /
25
+ schedule:
26
+ interval: monthly
27
+ cooldown:
28
+ default-days: 7
29
+ semver-major-days: 14
30
+ groups:
31
+ hooks:
32
+ patterns: ["*"]
@@ -0,0 +1,19 @@
1
+ <!-- The title is a commit subject: type(scope): subject. Write about the change, never about the
2
+ conversation that led to it: no "as requested", no "the user wants". AGENTS.md has the rules. -->
3
+
4
+ ## What changes for a user
5
+
6
+ <!-- One or two sentences. The subjects of the feat, fix and perf commits become the release notes. -->
7
+
8
+ ## Why
9
+
10
+ <!-- The problem, with evidence: an issue, a finding, a run. -->
11
+
12
+ ## Done when
13
+
14
+ - [ ] A test fails without this change, and `make check` is green.
15
+ - [ ] Looked at for real (`make demo`, `make serve`), or listed in `.agent/DEFERRED_VERIFICATION.md`.
16
+ - [ ] Text, models and schema of the standard agree (`make schema`), if the protocol changed.
17
+ - [ ] `feat`, `fix` and `perf` subjects read as lines of release notes; a decision has its section in `docs/ARCHITECTURE.md`.
18
+ - [ ] Nothing names a company, a client or a real loop.
19
+ - [ ] The title and this text describe the change, not the conversation that led to it.
@@ -0,0 +1,69 @@
1
+ ---
2
+ name: CI
3
+
4
+ on:
5
+ push:
6
+ branches: [main]
7
+ pull_request:
8
+ branches: [main]
9
+ workflow_dispatch:
10
+
11
+ permissions: {}
12
+
13
+ concurrency:
14
+ group: ${{ github.workflow }}-${{ github.ref }}
15
+ cancel-in-progress: true
16
+
17
+ # Actions are pinned to commits, not tags: a tag can be moved onto other code.
18
+ # The comment names the release for the human reading it.
19
+ jobs:
20
+ check:
21
+ name: make check (${{ matrix.os }}, Python ${{ matrix.python }})
22
+ runs-on: ${{ matrix.os }}
23
+ permissions:
24
+ contents: read
25
+ strategy:
26
+ fail-fast: false
27
+ matrix:
28
+ os: [ubuntu-latest]
29
+ python: ["3.12", "3.13", "3.14"]
30
+ include:
31
+ # The board starts processes and reads process groups: check it where people run it.
32
+ - os: macos-latest
33
+ python: "3.12"
34
+ steps:
35
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
36
+ with:
37
+ persist-credentials: false
38
+
39
+ - name: Install uv
40
+ uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3
41
+ with:
42
+ python-version: ${{ matrix.python }}
43
+
44
+ - name: Install
45
+ run: uv sync --frozen
46
+
47
+ # No model and no network: the tests run the example loop, which calls no agent.
48
+ - name: Check
49
+ run: make check
50
+ env:
51
+ UV_NO_SYNC: "1"
52
+
53
+ secrets:
54
+ name: gitleaks
55
+ runs-on: ubuntu-latest
56
+ permissions:
57
+ contents: read
58
+ steps:
59
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
60
+ with:
61
+ fetch-depth: 0 # a secret committed once and reverted is still leaked
62
+ persist-credentials: false
63
+
64
+ # The gitleaks CLI directly, pinned by digest: the same release as the pre-commit hook.
65
+ - name: Scan history for secrets
66
+ run: >-
67
+ docker run --rm -v "$PWD:/repo"
68
+ ghcr.io/gitleaks/gitleaks:v8.30.1@sha256:c00b6bd0aeb3071cbcb79009cb16a60dd9e0a7c60e2be9ab65d25e6bc8abbb7f
69
+ git /repo --redact --verbose --exit-code 1
@@ -0,0 +1,57 @@
1
+ name: docs
2
+
3
+ # Builds the documentation site on every change to main, and publishes it to GitHub Pages with
4
+ # the official flow only when the repository variable PUBLISH_DOCS is "true". A Pages site is
5
+ # public even when the repository is private, so publishing waits for the decision to go public.
6
+ # Only the deploy job may write to Pages or mint an identity token, in the `github-pages`
7
+ # environment. No versioned docs before 1.0.
8
+
9
+ on:
10
+ push:
11
+ branches: [main]
12
+ workflow_dispatch:
13
+
14
+ permissions: {}
15
+
16
+ concurrency:
17
+ group: pages
18
+ cancel-in-progress: false
19
+
20
+ jobs:
21
+ build:
22
+ runs-on: ubuntu-latest
23
+ permissions:
24
+ contents: read
25
+ steps:
26
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
27
+ with:
28
+ persist-credentials: false
29
+
30
+ - uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3
31
+ with:
32
+ python-version: "3.12"
33
+
34
+ - name: Build
35
+ run: |
36
+ uv sync --frozen
37
+ uv run --group docs zensical build --clean
38
+ cp docs/llms.txt site/llms.txt
39
+
40
+ - uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
41
+ with:
42
+ path: site/
43
+
44
+ deploy:
45
+ needs: build
46
+ if: vars.PUBLISH_DOCS == 'true'
47
+ runs-on: ubuntu-latest
48
+ environment:
49
+ name: github-pages
50
+ url: ${{ steps.deployment.outputs.page_url }}
51
+ permissions:
52
+ pages: write
53
+ id-token: write
54
+ steps:
55
+ - uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0
56
+ - id: deployment
57
+ uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5.0.1
@@ -0,0 +1,28 @@
1
+ name: links
2
+
3
+ # Weekly: do the links in the documents still lead somewhere? Never a gate: a site that is down
4
+ # for an hour must not block a change. A failure opens nothing; read the run.
5
+
6
+ on:
7
+ schedule:
8
+ - cron: "41 6 * * 1"
9
+ workflow_dispatch:
10
+
11
+ permissions: {}
12
+
13
+ jobs:
14
+ check:
15
+ runs-on: ubuntu-latest
16
+ permissions:
17
+ contents: read
18
+ steps:
19
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
20
+ with:
21
+ persist-credentials: false
22
+ - uses: lycheeverse/lychee-action@e7477775783ea5526144ba13e8db5eec57747ce8 # v2.9.0
23
+ with:
24
+ args: >-
25
+ --no-progress --max-retries 3
26
+ --exclude '^https?://127\.0\.0\.1'
27
+ './**/*.md'
28
+ fail: true
@@ -0,0 +1,62 @@
1
+ name: pr-description
2
+
3
+ # The commit-msg hooks keep AI attribution and conversation out of commits and hold the subject
4
+ # to Conventional Commits, but nothing local ever sees a pull request's title or description.
5
+ # This runs the same hooks on both, so each pattern lives in one place
6
+ # (.pre-commit-config.yaml). Separate from ci.yml so editing a description re-runs
7
+ # only this, not the whole gate.
8
+
9
+ on:
10
+ pull_request:
11
+ types: [opened, edited, reopened, synchronize]
12
+
13
+ permissions: {}
14
+
15
+ jobs:
16
+ attribution:
17
+ runs-on: ubuntu-latest
18
+ permissions:
19
+ contents: read
20
+ steps:
21
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
22
+ with:
23
+ persist-credentials: false
24
+
25
+ - uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
26
+ with:
27
+ python-version: '3.12'
28
+
29
+ # Through env, never interpolated into the script: the description is untrusted text.
30
+ - name: Write the PR description to a file
31
+ env:
32
+ BODY: ${{ github.event.pull_request.body }}
33
+ run: printf '%s\n' "$BODY" > "$RUNNER_TEMP/pr-description.txt"
34
+
35
+ - name: Reject AI attribution in the PR description
36
+ uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1
37
+ with:
38
+ extra_args: >-
39
+ no-agent-coauthors --hook-stage commit-msg
40
+ --commit-msg-filename ${{ runner.temp }}/pr-description.txt
41
+
42
+ # The description is about the change, never about the conversation that led to it
43
+ # (AGENTS.md, "Commits and pull requests").
44
+ - name: Reject conversation in the PR description
45
+ uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1
46
+ with:
47
+ extra_args: >-
48
+ no-conversation --hook-stage commit-msg
49
+ --commit-msg-filename ${{ runner.temp }}/pr-description.txt
50
+
51
+ # The title becomes a line of the changelog when a pull request is squashed.
52
+ - name: Write the PR title to a file
53
+ env:
54
+ TITLE: ${{ github.event.pull_request.title }}
55
+ run: printf '%s\n' "$TITLE" > "$RUNNER_TEMP/pr-title.txt"
56
+
57
+ - name: The PR title is a Conventional Commit subject
58
+ uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1
59
+ with:
60
+ extra_args: >-
61
+ conventional-commit --hook-stage commit-msg
62
+ --commit-msg-filename ${{ runner.temp }}/pr-title.txt
@@ -0,0 +1,108 @@
1
+ name: release
2
+
3
+ # Every push to main that brings a feat, fix or perf commit gets the next version as a tag and a
4
+ # GitHub release, with those commits as its notes and the built wheel attached
5
+ # (scripts/release.py, docs/ARCHITECTURE.md section 19). Nothing is committed back, so nobody
6
+ # bumps a version, pushes a tag or opens a pull request for one. The wheel is stamped with the
7
+ # tag's version in the runner, proved to work installed outside a checkout, and published to PyPI
8
+ # by trusted publishing (no token stored anywhere) once the repository variable PUBLISH_PYPI is
9
+ # "true".
10
+
11
+ on:
12
+ push:
13
+ branches: [main]
14
+ workflow_dispatch:
15
+
16
+ permissions: {}
17
+
18
+ # One release at a time, in push order, so two merges never take the same tag.
19
+ concurrency:
20
+ group: release
21
+ cancel-in-progress: false
22
+
23
+ jobs:
24
+ release:
25
+ name: tag and release
26
+ runs-on: ubuntu-latest
27
+ timeout-minutes: 15
28
+ permissions:
29
+ contents: write # the tag and the release
30
+ outputs:
31
+ tag: ${{ steps.next.outputs.tag }}
32
+ steps:
33
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
34
+ with:
35
+ fetch-depth: 0 # every tag and the commits since the last one
36
+ persist-credentials: false
37
+
38
+ - uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3
39
+ with:
40
+ python-version: "3.12"
41
+
42
+ - name: Next version
43
+ id: next
44
+ run: echo "tag=$(uv run --no-project python scripts/release.py --notes "$RUNNER_TEMP/release-notes.md")" >> "$GITHUB_OUTPUT"
45
+
46
+ - name: Stamp the version and build
47
+ if: steps.next.outputs.tag != ''
48
+ env:
49
+ TAG: ${{ steps.next.outputs.tag }}
50
+ run: |
51
+ uv version "${TAG#v}"
52
+ uv build
53
+
54
+ - name: Install the wheel in a clean environment and use it
55
+ if: steps.next.outputs.tag != ''
56
+ env:
57
+ TAG: ${{ steps.next.outputs.tag }}
58
+ run: |
59
+ uv venv /tmp/clean
60
+ uv pip install --python /tmp/clean/bin/python dist/*.whl
61
+ cd /tmp
62
+ test "$(/tmp/clean/bin/loopcaptain --version)" = "loopcaptain ${TAG#v}"
63
+ /tmp/clean/bin/loopcaptain spec > /dev/null
64
+ /tmp/clean/bin/loopcaptain demo --target /tmp/demo --no-watch
65
+ /tmp/clean/bin/loopcaptain validate --strict /tmp/demo
66
+
67
+ - name: Tag and release
68
+ if: steps.next.outputs.tag != ''
69
+ env:
70
+ GH_TOKEN: ${{ github.token }}
71
+ GH_REPO: ${{ github.repository }}
72
+ TAG: ${{ steps.next.outputs.tag }}
73
+ run: >-
74
+ gh release create "$TAG" dist/*.whl dist/*.tar.gz --target "$GITHUB_SHA" --title "$TAG"
75
+ --notes-file "$RUNNER_TEMP/release-notes.md"
76
+
77
+ - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
78
+ if: steps.next.outputs.tag != ''
79
+ with:
80
+ name: dist
81
+ path: |
82
+ dist/*.whl
83
+ dist/*.tar.gz
84
+ if-no-files-found: error
85
+
86
+ publish:
87
+ needs: release
88
+ if: needs.release.outputs.tag != '' && vars.PUBLISH_PYPI == 'true'
89
+ runs-on: ubuntu-latest
90
+ environment: pypi
91
+ permissions:
92
+ id-token: write # trusted publishing to PyPI
93
+ attestations: write # build provenance
94
+ contents: read
95
+ steps:
96
+ - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
97
+ with:
98
+ name: dist
99
+ path: dist/
100
+
101
+ - uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2
102
+ with:
103
+ subject-path: dist/*
104
+
105
+ - uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3
106
+
107
+ - name: Publish
108
+ run: uv publish --trusted-publishing always dist/*
@@ -0,0 +1,35 @@
1
+ name: scorecard
2
+
3
+ # The OpenSSF Scorecard, weekly: how the repository's own practices look to a stranger who has
4
+ # to decide whether to trust it. Results go to the code scanning tab, never block a change.
5
+
6
+ on:
7
+ schedule:
8
+ - cron: "17 5 * * 1"
9
+ push:
10
+ branches: [main]
11
+ workflow_dispatch:
12
+
13
+ permissions: {}
14
+
15
+ jobs:
16
+ analysis:
17
+ # The Scorecard reads a repository as a stranger would, so it runs only once it is public.
18
+ if: ${{ !github.event.repository.private }}
19
+ runs-on: ubuntu-latest
20
+ permissions:
21
+ contents: read
22
+ security-events: write
23
+ id-token: write # publishes the result to the OpenSSF API
24
+ steps:
25
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
26
+ with:
27
+ persist-credentials: false
28
+ - uses: ossf/scorecard-action@fa7117db83c52d787ae5a89f350999701505d78e # v2.5.0
29
+ with:
30
+ results_file: results.sarif
31
+ results_format: sarif
32
+ publish_results: true
33
+ - uses: github/codeql-action/upload-sarif@33d7b85da1a04171e211befe2c739303b9a4c066 # v4.39.3
34
+ with:
35
+ sarif_file: results.sarif
@@ -0,0 +1,37 @@
1
+ name: security
2
+
3
+ # What the code review of a workflow and a dependency cannot see by eye: zizmor reads every
4
+ # workflow for injection and credential leaks; dependency review refuses a pull request that adds
5
+ # a dependency with a known vulnerability. CodeQL runs by GitHub's default setup, a repository
6
+ # setting, not a workflow.
7
+
8
+ on:
9
+ push:
10
+ branches: [main]
11
+ pull_request:
12
+ branches: [main]
13
+
14
+ permissions: {}
15
+
16
+ jobs:
17
+ zizmor:
18
+ runs-on: ubuntu-latest
19
+ permissions:
20
+ contents: read
21
+ steps:
22
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
23
+ with:
24
+ persist-credentials: false
25
+ - uses: zizmorcore/zizmor-action@45d7276c9ca790aabd705e31ca99c6185c3e76dd # v0.8.1
26
+ with:
27
+ advanced-security: false
28
+
29
+ dependencies:
30
+ if: github.event_name == 'pull_request'
31
+ runs-on: ubuntu-latest
32
+ permissions:
33
+ contents: read
34
+ steps:
35
+ - uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0
36
+ with:
37
+ fail-on-severity: moderate
@@ -0,0 +1,23 @@
1
+ # Finder and editor clutter
2
+ .DS_Store
3
+ /.serena/
4
+
5
+ # Environments, caches, coverage
6
+ /.venv/
7
+ __pycache__/
8
+ /.pytest_cache/
9
+ /.mypy_cache/
10
+ /.ruff_cache/
11
+ /.coverage
12
+ /site/
13
+
14
+ # Secrets
15
+ /.env
16
+
17
+ # Which repositories this laptop's board watches, the demo loop's working
18
+ # folder, and the logs of processes the board started
19
+ *.local
20
+ *.local.md
21
+ *.local.yaml
22
+ /demo/
23
+ /.state/