foundry-implementation-actor 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,217 @@
1
+ Metadata-Version: 2.5
2
+ Name: foundry-implementation-actor
3
+ Version: 0.1.0
4
+ Summary: Runs a headless Claude Code implementation session against one capability's own repo — a papeete-actor for one use, with the capability supplied by a sidecar.
5
+ Project-URL: Homepage, https://github.com/papeete-hub/foundry-implementation-actor
6
+ Author-email: Papeete Consulting <yoann.remy@outlook.com>
7
+ License-Expression: MIT
8
+ Keywords: actor-model,agentic,capability,claude-code,papeete
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Topic :: Software Development :: Code Generators
13
+ Requires-Python: >=3.11
14
+ Requires-Dist: papeete-actor-synchronous-messaging>=0.2.1
15
+ Requires-Dist: papeete-version>=0.1.0
16
+ Requires-Dist: pyyaml>=6.0
17
+ Provides-Extra: dev
18
+ Requires-Dist: pytest>=8.0; extra == 'dev'
19
+ Description-Content-Type: text/markdown
20
+
21
+ # foundry-implementation-actor
22
+
23
+ Runs a headless [Claude Code](https://claude.com/claude-code) implementation session against one
24
+ capability's own repository, then commits, pushes and publishes what it produced — inside a write
25
+ boundary the actor enforces rather than requests.
26
+
27
+ An **actor, for one use**, with a [`papeete-actor`](https://github.com/papeete-hub/papeete-actor)
28
+ underneath. The `-actor` suffix is that claim; `papeete-actor-*` names, by contrast, are transverse
29
+ features *of* the framework (`ADR-ECO-0022`).
30
+
31
+ ```bash
32
+ pip install foundry-implementation-actor
33
+ ```
34
+
35
+ ## What it is
36
+
37
+ The machinery. It carries **no capability of its own** — the capability it serves arrives in a
38
+ sidecar the consuming repo writes:
39
+
40
+ ```yaml
41
+ # actor-agentic-context.yaml
42
+ context: foundry-implementation-actor/agentic-context/v1
43
+ engine: claude-code
44
+ capability: ACME.PARTS.CAP.SUP.007.WID
45
+ source_repo: acme-lab/ACME.PARTS.CAP.SUP.007.WID-implementation
46
+ registry_repo: acme-lab/acme-governance
47
+ components:
48
+ - {name: backend, path: backend/, tests: backend/tests/, dockerfile: backend/deployment/local}
49
+ - {name: stub, path: stub/, tests: stub/tests/, dockerfile: stub/deployment/local}
50
+ ground_in:
51
+ - name: business
52
+ answers: the WHAT/WHY — domain vision, business events, ubiquitous language
53
+ fetch: [kpack, pack, "{capability}", --deep, --compact, --registry-repo, "{registry_repo}"]
54
+ into: .foundry/business.md
55
+ load: eager
56
+ - name: process
57
+ answers: the HOW — aggregates, commands, policies, read-models, bus, api, JSON schemas
58
+ fetch: [kontract, fetch, "{capability}", --compact, --registry-repo, "{registry_repo}"]
59
+ into: .foundry/process.md
60
+ load: on-demand
61
+ ```
62
+
63
+ and four lines in the consuming entrypoint:
64
+
65
+ ```python
66
+ from foundry_implementation_actor import CapabilityConfig, ClaudeCodeEngine, make_implement_task
67
+
68
+ config = CapabilityConfig.load(".")
69
+ actor = Actor.from_card(".", mailbox=mailbox,
70
+ engines={config.engine: ClaudeCodeEngine(config)},
71
+ actions={"implement-task": make_implement_task(config)})
72
+ ```
73
+
74
+ A second capability instantiates the same actor by writing that file. Nothing here is subclassed,
75
+ hooked, or configured with a strategy object — there is one shape, and it is this one.
76
+
77
+ ## What one request does
78
+
79
+ ```
80
+ clone (full, not --depth 1)
81
+ → checkout impl/TASK-NNN
82
+ → run every ground_in fetch, write it into the clone, render CLAUDE.md
83
+ → claude --print --output-format stream-json --permission-mode acceptEdits
84
+ → git add <each component root>; refuse anything staged outside them
85
+ → commit as the actor, push the branch
86
+ → buildctl build + push one image per touched component
87
+ ```
88
+
89
+ It never opens a pull request. Rendering a verdict and opening one belongs to whichever actor
90
+ orchestrates the pipeline, once its other members have also confirmed.
91
+
92
+ ## Grounding is a precondition, not a request
93
+
94
+ `CLAUDE.md` at the working directory's root is loaded by the `claude` CLI **before the first
95
+ turn**, and its `@relative/path.md` imports resolve eagerly at the same moment. Verified live in
96
+ this actor's exact invocation shape: a question only the fetched context could answer came back in
97
+ `num_turns: 1` with zero tool calls.
98
+
99
+ So the envelopes are written **inside the clone** and named from a generated `CLAUDE.md`. The
100
+ earlier arrangement — fetch into a sibling tempdir, then ask the session in prose to "read it
101
+ before you start" — was not broken, it was *advisory*: whether the context entered the window was
102
+ the model's choice, re-made every session, and a session that skipped it looked exactly like one
103
+ that had read it. An `@`-import resolves relative to the file containing it, so the sibling-tempdir
104
+ arrangement could not have been fixed by writing a better prompt.
105
+
106
+ If the repo commits its own `CLAUDE.md`, the generated block is **appended**, never substituted.
107
+
108
+ ### The two tiers
109
+
110
+ | `load:` | cost | when |
111
+ |---|---|---|
112
+ | `eager` | its full token weight, every session, unconditionally | the session cannot do the work without it |
113
+ | `on-demand` | one line — its `answers:` and its path | useful sometimes; the session opens it if the task needs it |
114
+
115
+ Choose from a measurement of the envelope, not from taste. One such read has already been recorded
116
+ as putting *"40 kB of JSON on screen"*.
117
+
118
+ ## One capability id, eight renderings, zero literals
119
+
120
+ Every identifier is derived from `capability` and `source_repo`. `foundry-implementation-actor
121
+ show` prints the table for a given sidecar:
122
+
123
+ | rendering | from |
124
+ |---|---|
125
+ | `ACME.PARTS.CAP.SUP.007.WID` | `capability` |
126
+ | `acme-lab/ACME.PARTS.CAP.SUP.007.WID-implementation` | `source_repo` |
127
+ | `acme.parts/sup.007.wid` | the id, split at its `CAP` segment |
128
+ | `acme.parts.cap.sup.007.wid-backend` | the image name `papeete-version` versions |
129
+ | `acme-parts-cap-sup-007-wid-implementation-TASK-042-` | the clone's tempdir prefix |
130
+ | `ACME.PARTS.CAP.SUP.007.WID-implementation` | git `user.name` |
131
+ | `acme-parts-cap-sup-007-wid-implementation@users.noreply.github.com` | git `user.email` |
132
+ | `<registry>/acme.parts/sup.007.wid/<component>:<version>` | the published image ref |
133
+
134
+ **The image ref is a three-way contract.** Peer actors recompute the identical string and parse it
135
+ back apart, so it is derivation output or nothing. Never invent a tag scheme.
136
+
137
+ ## The write boundary is stated once
138
+
139
+ `components[].path` — and nothing else. It used to be declared in the sidecar *and* hardcoded in
140
+ the module that actually enforces containment, which is how a boundary comes to be stated twice
141
+ and eventually stated differently.
142
+
143
+ Containment is `git add <each root>`, never `-A` and never a bare `.`, then an assertion that every
144
+ staged path starts with one of them. A staged path outside is **refused**, the index reset, and the
145
+ door answers a refusal. The session's own prompt only asks for the boundary; this is what holds it.
146
+
147
+ Which component a staged path belongs to is resolved by **longest declared prefix**, not by taking
148
+ the path's first segment — that shortcut is correct only while every root is one segment deep, and
149
+ reports the wrong component the day one of them is `src/gateway/`.
150
+
151
+ ## Credentials
152
+
153
+ Two, both passed at run time, never baked into an image:
154
+
155
+ | variable | what for |
156
+ |---|---|
157
+ | `GITHUB_TOKEN` | fine-grained PAT: `contents:write` on `source_repo`, read-only `contents` on whatever the `ground_in` fetches resolve through |
158
+ | `CLAUDE_CODE_OAUTH_TOKEN` | from `claude setup-token` on a machine with a browser, tied to a Pro/Max/Team/Enterprise subscription |
159
+
160
+ > **Do not also set `ANTHROPIC_API_KEY` or `ANTHROPIC_AUTH_TOKEN`.** In `claude -p`
161
+ > non-interactive mode an API key present in the environment is ALWAYS preferred over
162
+ > `CLAUDE_CODE_OAUTH_TOKEN`, silently routing every session through metered billing. There is no
163
+ > warning and no visible difference in the transcript; the only symptom is the bill.
164
+
165
+ Publishing additionally needs `IMAGE_REGISTRY` and `BUILDKIT_HOST`. There is no Docker daemon and
166
+ no docker socket anywhere in this design — `buildctl` is a client, which is why an actor running
167
+ this can be an ordinary Pod.
168
+
169
+ ## CLI
170
+
171
+ ```bash
172
+ foundry-implementation-actor lint . # validate the sidecar
173
+ foundry-implementation-actor show . --registry reg.example.com # every derived rendering
174
+ ```
175
+
176
+ `lint` is the gate CI runs. A sidecar declaring some other `context:` is read, warned, and not
177
+ checked further — UNMIGRATED is not the same as non-conformant, and migrating is the owning pair's
178
+ own act.
179
+
180
+ ## Observability
181
+
182
+ One JSON object per step, so a dashboard reads them with the same `| json` it uses for the session
183
+ transcript and tells them apart by `event`:
184
+
185
+ ```
186
+ {level, event: "step", step, phase: start|ok|failed, duration_ms, error}
187
+ {level, event: "event", step, **fields}
188
+ {level, event: "result", cost_usd, input_tokens, output_tokens}
189
+ correlation_id, task_id # structured metadata
190
+ ```
191
+
192
+ **That schema is the contract; the step names are not.** Step names may be renamed freely — a
193
+ drill-down discovers them. `correlation_id` is the W3C trace id the caller propagated, not a second
194
+ identifier invented here, so it pastes straight from Loki into Tempo.
195
+
196
+ Every emitted line is budgeted to 64 KB. Loki's `max_line_size` is 256 KB with
197
+ `max_line_size_truncate: false` — an oversized line is **rejected outright**, not trimmed, so one
198
+ large `Read` would silently delete exactly the turn worth reading while leaving the rest intact.
199
+
200
+ ## Where this came from
201
+
202
+ Extracted from one capability's own implementation actor, where 891 lines of this machinery sat
203
+ beside the business capability's source. Both files that moved said so themselves: the engine's
204
+ docstring called extraction *"a near-mechanical move"*, and the sidecar's header said it was kept
205
+ structural *"specifically so extracting this into a real published package later (once proven) is a
206
+ move, not a rewrite."*
207
+
208
+ `adr/` records the decisions. Design rationale belongs there, not in commit messages.
209
+
210
+ ## Development
211
+
212
+ ```bash
213
+ uv run --extra dev pytest -q # what CI runs
214
+ uv build
215
+ ```
216
+
217
+ There is no separate lint/format command configured in this repo.
@@ -0,0 +1,12 @@
1
+ foundry_implementation_actor/__init__.py,sha256=MN--k8NLJM5erROXO2sWH-7kRUaht_myBYmVRgAWFCM,1448
2
+ foundry_implementation_actor/cli.py,sha256=tgVtsMeX7AC97monFrNF85vfWPhN1T_aPk65OGcuyd0,3887
3
+ foundry_implementation_actor/config.py,sha256=OT3EthNUvSUIHiWv_FQQ9kwjpeTQgQhAQjw3NlWFP_Y,18318
4
+ foundry_implementation_actor/correlation.py,sha256=PmnlGDKzu9ZBB8NEHaB4mNTimYZSHdWw31BNFbb0nNo,10658
5
+ foundry_implementation_actor/engine.py,sha256=6C5_DM5sxrdRruUgzgWAeJlDObDdcaDp0jDyeqSig7o,22898
6
+ foundry_implementation_actor/grounding.py,sha256=X-hZ6XuYNrHcNoDx7uNVCUMbUUBvKoVbM4CohSJa6as,8907
7
+ foundry_implementation_actor/handler.py,sha256=WbpEPdLqEH0EVEl1Kl18u8vk49GUEsQlaEmqT4aJU0Q,10697
8
+ foundry_implementation_actor/schemas/agentic-context.schema.yaml,sha256=Wxh3e1Eqk4z0lqV2Tlvvofyc0nYdz94JruWqm-QKqbY,6651
9
+ foundry_implementation_actor-0.1.0.dist-info/METADATA,sha256=gJn0UP0AkKyiaHyjy-z-wvp4wyOR_lh7o7w1c6Rjygw,10122
10
+ foundry_implementation_actor-0.1.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
11
+ foundry_implementation_actor-0.1.0.dist-info/entry_points.txt,sha256=gTDSlXCPXPDXpi8WflBUapIfx_fuqsq7qsBUkVDIXOU,87
12
+ foundry_implementation_actor-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ foundry-implementation-actor = foundry_implementation_actor.cli:main