frontface-flashy 1.0.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.
- frontface_flashy-1.0.0/LICENSE +21 -0
- frontface_flashy-1.0.0/PKG-INFO +175 -0
- frontface_flashy-1.0.0/README.md +159 -0
- frontface_flashy-1.0.0/pyproject.toml +27 -0
- frontface_flashy-1.0.0/setup.cfg +4 -0
- frontface_flashy-1.0.0/src/flashy/__init__.py +42 -0
- frontface_flashy-1.0.0/src/flashy/checkpoints.py +195 -0
- frontface_flashy-1.0.0/src/flashy/detectors.py +111 -0
- frontface_flashy-1.0.0/src/flashy/ledger.py +132 -0
- frontface_flashy-1.0.0/src/flashy/quality_gate.py +51 -0
- frontface_flashy-1.0.0/src/flashy/session.py +181 -0
- frontface_flashy-1.0.0/src/flashy/state_machine.py +117 -0
- frontface_flashy-1.0.0/src/frontface_flashy.egg-info/PKG-INFO +175 -0
- frontface_flashy-1.0.0/src/frontface_flashy.egg-info/SOURCES.txt +17 -0
- frontface_flashy-1.0.0/src/frontface_flashy.egg-info/dependency_links.txt +1 -0
- frontface_flashy-1.0.0/src/frontface_flashy.egg-info/top_level.txt +1 -0
- frontface_flashy-1.0.0/tests/test_checkpoints_detectors_session.py +240 -0
- frontface_flashy-1.0.0/tests/test_ledger.py +59 -0
- frontface_flashy-1.0.0/tests/test_state_machine_gate.py +143 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ryan Wesley Hitchcock
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: frontface-flashy
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Persistent execution discipline for long tasks: lock the objective, track milestones, detect drift and artificial stops, verify before done
|
|
5
|
+
Author: Ryan Wesley Hitchcock
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/ohpersonperson/frontface
|
|
8
|
+
Keywords: execution,task-management,state-machine,agents,workflow
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Requires-Python: >=3.10
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Dynamic: license-file
|
|
16
|
+
|
|
17
|
+
# Flashy — persistent execution discipline
|
|
18
|
+
|
|
19
|
+
Prevents an agent from abandoning a build halfway. Locks the objective,
|
|
20
|
+
expands it into milestones, tracks live execution state, scans for drift
|
|
21
|
+
after every milestone, detects artificial stops ("here's a starting
|
|
22
|
+
point"), checkpoints honestly on genuine limits, and gates completion on
|
|
23
|
+
a quality checklist.
|
|
24
|
+
|
|
25
|
+
Mission rule: **a draft is not a delivery.** Completion is measured by
|
|
26
|
+
whether the user actually received the usable thing they asked for.
|
|
27
|
+
|
|
28
|
+
No prerequisites. The mechanism is the whole product.
|
|
29
|
+
|
|
30
|
+
*(Lineage: this is the front-facing conversion of the `flashy` skill from
|
|
31
|
+
Ryan's skill-suite, MIT — itself a port of an execution-discipline
|
|
32
|
+
protocol into skill format. What changed in the conversion is documented
|
|
33
|
+
in CHANGELOG.md.)*
|
|
34
|
+
|
|
35
|
+
## What it does
|
|
36
|
+
|
|
37
|
+
For any substantial build/create/write task, run the core loop — **lock,
|
|
38
|
+
move, verify**:
|
|
39
|
+
|
|
40
|
+
1. **Lock the objective.** Extract three things from the request: the
|
|
41
|
+
stated deliverable, the implicit quality bar ("a login form" implies
|
|
42
|
+
validation and error states even if unsaid), and the implicit scope
|
|
43
|
+
boundaries. Once locked, the objective is immutable for the rest of the
|
|
44
|
+
task: subtasks don't replace the mission, explanations don't replace
|
|
45
|
+
execution, planning doesn't replace progress.
|
|
46
|
+
2. **Expand into milestones.** Objective → Major Milestones → Subtasks →
|
|
47
|
+
Implementation Steps → Verification → Delivery.
|
|
48
|
+
3. **Track execution state.** A real state machine —
|
|
49
|
+
`LOCKED → PLANNING → EXECUTING → VERIFYING → DONE`, with `BLOCKED`
|
|
50
|
+
reachable from `EXECUTING`/`VERIFYING` — enforces the transition rules
|
|
51
|
+
in code: no `VERIFYING → DONE` without every Quality Gate box checked,
|
|
52
|
+
no silent `EXECUTING → PLANNING` without an explicit user-driven scope
|
|
53
|
+
change, no `BLOCKED → EXECUTING` without an emitted checkpoint.
|
|
54
|
+
4. **Scan for drift after every milestone.** Four questions: still solving
|
|
55
|
+
the original problem? wandered into explanation instead of building?
|
|
56
|
+
forgot earlier requirements? would the user call this finished? On
|
|
57
|
+
drift: discard the thread, restate the objective, resume from the last
|
|
58
|
+
verified milestone.
|
|
59
|
+
5. **Detect artificial stops.** "Here's a starting point," "the rest follows
|
|
60
|
+
similarly," "due to space..." — these are execution failures, not
|
|
61
|
+
conclusions. The package scans output for the phrase list and flags
|
|
62
|
+
them; the operator rewrites the impulse as **Continue Working.**
|
|
63
|
+
6. **Checkpoint honestly on genuine limits.** Context cutoff, tool error,
|
|
64
|
+
or a low-confidence blocker with no safe default — emit a checkpoint
|
|
65
|
+
with a resume token (`finish:<slug>-m<N>`) and continue next turn.
|
|
66
|
+
Fatigue and "this feels done" are failures, not checkpoints.
|
|
67
|
+
7. **Verify before done.** The seven-box Quality Gate is a hard gate, not
|
|
68
|
+
a vibe check. Any unchecked box = IN PROGRESS.
|
|
69
|
+
|
|
70
|
+
**Execution bias: build before briefing.** Build → Verify → Polish →
|
|
71
|
+
Explain. Understanding the problem well enough to act is the signal to
|
|
72
|
+
stop analyzing and start executing. And the end-of-response gate: a
|
|
73
|
+
response ending is not a task ending — if useful progress can still be
|
|
74
|
+
made, keep executing; don't hand the turn back for permission.
|
|
75
|
+
|
|
76
|
+
**Pause only for implementation-changing decisions** — choice of
|
|
77
|
+
database/language/framework, deployment target, a required credential, or
|
|
78
|
+
an ambiguity where two reasonable interpretations produce meaningfully
|
|
79
|
+
different deliverables. "Continue?" / "Should I keep going?" are never
|
|
80
|
+
valid stopping points.
|
|
81
|
+
|
|
82
|
+
## Why it works
|
|
83
|
+
|
|
84
|
+
Most abandoned builds don't fail — they *decay*. The objective quietly
|
|
85
|
+
gets replaced by a subtask. Explanation replaces execution. A response
|
|
86
|
+
"feels long enough" and stops. Flashy attacks the decay mechanically:
|
|
87
|
+
|
|
88
|
+
- **The state machine** makes the rules structural: you cannot declare
|
|
89
|
+
done without the gate, cannot silently re-plan, cannot resume from a
|
|
90
|
+
block without a checkpoint. The failure modes are `TransitionError`s,
|
|
91
|
+
not suggestions.
|
|
92
|
+
- **Artificial-stop detection** catches the exact phrases long tasks die
|
|
93
|
+
on — as a string scan, in code, every time.
|
|
94
|
+
- **The confidence engine** (High/Medium/Low) resolves ambiguity without
|
|
95
|
+
blocking: High proceeds silently, Medium proceeds stating the
|
|
96
|
+
assumption inline, only Low pauses. Most ambiguity never surfaces as a
|
|
97
|
+
stop.
|
|
98
|
+
- **Checkpoints with resume tokens** make interruption recoverable without
|
|
99
|
+
the user re-explaining anything — which removes the incentive to
|
|
100
|
+
quietly abandon a task at a session break.
|
|
101
|
+
|
|
102
|
+
## Quickstart
|
|
103
|
+
|
|
104
|
+
Zero dependencies beyond Python 3.10+. No model, no network — this
|
|
105
|
+
package is pure discipline machinery.
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
cd flashy
|
|
109
|
+
python3 -m unittest discover -s tests # 41 offline tests — prove the machinery first
|
|
110
|
+
python3 examples/demo_session.py # watch a session run LOCKED -> DONE
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Or use it as a library:
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
from flashy import MissionSession
|
|
117
|
+
|
|
118
|
+
session = MissionSession(
|
|
119
|
+
"Deliver a functional, usable, in-browser debate simulator.",
|
|
120
|
+
slug="debate-sim",
|
|
121
|
+
)
|
|
122
|
+
session.plan(["Architecture", "UI", "Logic", "Persistence", "Testing", "Delivery"])
|
|
123
|
+
session.start_executing()
|
|
124
|
+
|
|
125
|
+
for milestone in ["Architecture", "UI", "Logic", "Persistence", "Testing", "Delivery"]:
|
|
126
|
+
assert session.end_of_response_gate() == "continue"
|
|
127
|
+
session.complete_milestone(milestone)
|
|
128
|
+
print(session.dashboard())
|
|
129
|
+
|
|
130
|
+
session.begin_verification()
|
|
131
|
+
session.gate.check_all()
|
|
132
|
+
session.finish() # raises unless every box is checked
|
|
133
|
+
assert session.machine.state == "DONE"
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Inputs, outputs
|
|
137
|
+
|
|
138
|
+
- **Input:** a locked objective (the finished thing, not the artifact
|
|
139
|
+
type), milestones, constraints, pending decisions.
|
|
140
|
+
- **Output:** a driven session — dashboard state, checkpoints with resume
|
|
141
|
+
tokens, and a DONE that actually means done. The package never writes
|
|
142
|
+
files; your harness owns persistence.
|
|
143
|
+
|
|
144
|
+
## Worked examples
|
|
145
|
+
|
|
146
|
+
Three in `examples/`, a stranger can follow all of them:
|
|
147
|
+
|
|
148
|
+
1. `example-1-debate-simulator-walkthrough.md` — the full loop end to end
|
|
149
|
+
on a browser debate simulator (ported from the original skill, unchanged).
|
|
150
|
+
2. `example-2-anti-patterns.md` — before/after pairs: artificial-stop
|
|
151
|
+
language vs. the corrected continuation (ported, unchanged).
|
|
152
|
+
3. `example-3-flashy-session.md` — the machinery itself: every
|
|
153
|
+
transition, gate, and checkpoint shown as the code enforces it. Run
|
|
154
|
+
`demo_session.py` to watch it execute.
|
|
155
|
+
|
|
156
|
+
*All fields are constructed for teaching — they show the discipline's
|
|
157
|
+
shape, not real findings.*
|
|
158
|
+
|
|
159
|
+
## FAQ
|
|
160
|
+
|
|
161
|
+
**Does this make the model smarter?** No. It makes it finish things.
|
|
162
|
+
Execution discipline, not reasoning quality.
|
|
163
|
+
|
|
164
|
+
**Does it need an internet connection?** No. No model calls at all — the
|
|
165
|
+
41 unit tests and the demo run fully offline.
|
|
166
|
+
|
|
167
|
+
**What does it cost?** Nothing. Free, dependency-free, offline.
|
|
168
|
+
|
|
169
|
+
**When should it *not* fire?** Trivial tasks (a single short answer),
|
|
170
|
+
genuine user decisions pending, safety constraints, or real platform
|
|
171
|
+
limits. Never deactivate because a response "feels long enough."
|
|
172
|
+
|
|
173
|
+
## License
|
|
174
|
+
|
|
175
|
+
MIT. See LICENSE.
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# Flashy — persistent execution discipline
|
|
2
|
+
|
|
3
|
+
Prevents an agent from abandoning a build halfway. Locks the objective,
|
|
4
|
+
expands it into milestones, tracks live execution state, scans for drift
|
|
5
|
+
after every milestone, detects artificial stops ("here's a starting
|
|
6
|
+
point"), checkpoints honestly on genuine limits, and gates completion on
|
|
7
|
+
a quality checklist.
|
|
8
|
+
|
|
9
|
+
Mission rule: **a draft is not a delivery.** Completion is measured by
|
|
10
|
+
whether the user actually received the usable thing they asked for.
|
|
11
|
+
|
|
12
|
+
No prerequisites. The mechanism is the whole product.
|
|
13
|
+
|
|
14
|
+
*(Lineage: this is the front-facing conversion of the `flashy` skill from
|
|
15
|
+
Ryan's skill-suite, MIT — itself a port of an execution-discipline
|
|
16
|
+
protocol into skill format. What changed in the conversion is documented
|
|
17
|
+
in CHANGELOG.md.)*
|
|
18
|
+
|
|
19
|
+
## What it does
|
|
20
|
+
|
|
21
|
+
For any substantial build/create/write task, run the core loop — **lock,
|
|
22
|
+
move, verify**:
|
|
23
|
+
|
|
24
|
+
1. **Lock the objective.** Extract three things from the request: the
|
|
25
|
+
stated deliverable, the implicit quality bar ("a login form" implies
|
|
26
|
+
validation and error states even if unsaid), and the implicit scope
|
|
27
|
+
boundaries. Once locked, the objective is immutable for the rest of the
|
|
28
|
+
task: subtasks don't replace the mission, explanations don't replace
|
|
29
|
+
execution, planning doesn't replace progress.
|
|
30
|
+
2. **Expand into milestones.** Objective → Major Milestones → Subtasks →
|
|
31
|
+
Implementation Steps → Verification → Delivery.
|
|
32
|
+
3. **Track execution state.** A real state machine —
|
|
33
|
+
`LOCKED → PLANNING → EXECUTING → VERIFYING → DONE`, with `BLOCKED`
|
|
34
|
+
reachable from `EXECUTING`/`VERIFYING` — enforces the transition rules
|
|
35
|
+
in code: no `VERIFYING → DONE` without every Quality Gate box checked,
|
|
36
|
+
no silent `EXECUTING → PLANNING` without an explicit user-driven scope
|
|
37
|
+
change, no `BLOCKED → EXECUTING` without an emitted checkpoint.
|
|
38
|
+
4. **Scan for drift after every milestone.** Four questions: still solving
|
|
39
|
+
the original problem? wandered into explanation instead of building?
|
|
40
|
+
forgot earlier requirements? would the user call this finished? On
|
|
41
|
+
drift: discard the thread, restate the objective, resume from the last
|
|
42
|
+
verified milestone.
|
|
43
|
+
5. **Detect artificial stops.** "Here's a starting point," "the rest follows
|
|
44
|
+
similarly," "due to space..." — these are execution failures, not
|
|
45
|
+
conclusions. The package scans output for the phrase list and flags
|
|
46
|
+
them; the operator rewrites the impulse as **Continue Working.**
|
|
47
|
+
6. **Checkpoint honestly on genuine limits.** Context cutoff, tool error,
|
|
48
|
+
or a low-confidence blocker with no safe default — emit a checkpoint
|
|
49
|
+
with a resume token (`finish:<slug>-m<N>`) and continue next turn.
|
|
50
|
+
Fatigue and "this feels done" are failures, not checkpoints.
|
|
51
|
+
7. **Verify before done.** The seven-box Quality Gate is a hard gate, not
|
|
52
|
+
a vibe check. Any unchecked box = IN PROGRESS.
|
|
53
|
+
|
|
54
|
+
**Execution bias: build before briefing.** Build → Verify → Polish →
|
|
55
|
+
Explain. Understanding the problem well enough to act is the signal to
|
|
56
|
+
stop analyzing and start executing. And the end-of-response gate: a
|
|
57
|
+
response ending is not a task ending — if useful progress can still be
|
|
58
|
+
made, keep executing; don't hand the turn back for permission.
|
|
59
|
+
|
|
60
|
+
**Pause only for implementation-changing decisions** — choice of
|
|
61
|
+
database/language/framework, deployment target, a required credential, or
|
|
62
|
+
an ambiguity where two reasonable interpretations produce meaningfully
|
|
63
|
+
different deliverables. "Continue?" / "Should I keep going?" are never
|
|
64
|
+
valid stopping points.
|
|
65
|
+
|
|
66
|
+
## Why it works
|
|
67
|
+
|
|
68
|
+
Most abandoned builds don't fail — they *decay*. The objective quietly
|
|
69
|
+
gets replaced by a subtask. Explanation replaces execution. A response
|
|
70
|
+
"feels long enough" and stops. Flashy attacks the decay mechanically:
|
|
71
|
+
|
|
72
|
+
- **The state machine** makes the rules structural: you cannot declare
|
|
73
|
+
done without the gate, cannot silently re-plan, cannot resume from a
|
|
74
|
+
block without a checkpoint. The failure modes are `TransitionError`s,
|
|
75
|
+
not suggestions.
|
|
76
|
+
- **Artificial-stop detection** catches the exact phrases long tasks die
|
|
77
|
+
on — as a string scan, in code, every time.
|
|
78
|
+
- **The confidence engine** (High/Medium/Low) resolves ambiguity without
|
|
79
|
+
blocking: High proceeds silently, Medium proceeds stating the
|
|
80
|
+
assumption inline, only Low pauses. Most ambiguity never surfaces as a
|
|
81
|
+
stop.
|
|
82
|
+
- **Checkpoints with resume tokens** make interruption recoverable without
|
|
83
|
+
the user re-explaining anything — which removes the incentive to
|
|
84
|
+
quietly abandon a task at a session break.
|
|
85
|
+
|
|
86
|
+
## Quickstart
|
|
87
|
+
|
|
88
|
+
Zero dependencies beyond Python 3.10+. No model, no network — this
|
|
89
|
+
package is pure discipline machinery.
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
cd flashy
|
|
93
|
+
python3 -m unittest discover -s tests # 41 offline tests — prove the machinery first
|
|
94
|
+
python3 examples/demo_session.py # watch a session run LOCKED -> DONE
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Or use it as a library:
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
from flashy import MissionSession
|
|
101
|
+
|
|
102
|
+
session = MissionSession(
|
|
103
|
+
"Deliver a functional, usable, in-browser debate simulator.",
|
|
104
|
+
slug="debate-sim",
|
|
105
|
+
)
|
|
106
|
+
session.plan(["Architecture", "UI", "Logic", "Persistence", "Testing", "Delivery"])
|
|
107
|
+
session.start_executing()
|
|
108
|
+
|
|
109
|
+
for milestone in ["Architecture", "UI", "Logic", "Persistence", "Testing", "Delivery"]:
|
|
110
|
+
assert session.end_of_response_gate() == "continue"
|
|
111
|
+
session.complete_milestone(milestone)
|
|
112
|
+
print(session.dashboard())
|
|
113
|
+
|
|
114
|
+
session.begin_verification()
|
|
115
|
+
session.gate.check_all()
|
|
116
|
+
session.finish() # raises unless every box is checked
|
|
117
|
+
assert session.machine.state == "DONE"
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Inputs, outputs
|
|
121
|
+
|
|
122
|
+
- **Input:** a locked objective (the finished thing, not the artifact
|
|
123
|
+
type), milestones, constraints, pending decisions.
|
|
124
|
+
- **Output:** a driven session — dashboard state, checkpoints with resume
|
|
125
|
+
tokens, and a DONE that actually means done. The package never writes
|
|
126
|
+
files; your harness owns persistence.
|
|
127
|
+
|
|
128
|
+
## Worked examples
|
|
129
|
+
|
|
130
|
+
Three in `examples/`, a stranger can follow all of them:
|
|
131
|
+
|
|
132
|
+
1. `example-1-debate-simulator-walkthrough.md` — the full loop end to end
|
|
133
|
+
on a browser debate simulator (ported from the original skill, unchanged).
|
|
134
|
+
2. `example-2-anti-patterns.md` — before/after pairs: artificial-stop
|
|
135
|
+
language vs. the corrected continuation (ported, unchanged).
|
|
136
|
+
3. `example-3-flashy-session.md` — the machinery itself: every
|
|
137
|
+
transition, gate, and checkpoint shown as the code enforces it. Run
|
|
138
|
+
`demo_session.py` to watch it execute.
|
|
139
|
+
|
|
140
|
+
*All fields are constructed for teaching — they show the discipline's
|
|
141
|
+
shape, not real findings.*
|
|
142
|
+
|
|
143
|
+
## FAQ
|
|
144
|
+
|
|
145
|
+
**Does this make the model smarter?** No. It makes it finish things.
|
|
146
|
+
Execution discipline, not reasoning quality.
|
|
147
|
+
|
|
148
|
+
**Does it need an internet connection?** No. No model calls at all — the
|
|
149
|
+
41 unit tests and the demo run fully offline.
|
|
150
|
+
|
|
151
|
+
**What does it cost?** Nothing. Free, dependency-free, offline.
|
|
152
|
+
|
|
153
|
+
**When should it *not* fire?** Trivial tasks (a single short answer),
|
|
154
|
+
genuine user decisions pending, safety constraints, or real platform
|
|
155
|
+
limits. Never deactivate because a response "feels long enough."
|
|
156
|
+
|
|
157
|
+
## License
|
|
158
|
+
|
|
159
|
+
MIT. See LICENSE.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "frontface-flashy"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Persistent execution discipline for long tasks: lock the objective, track milestones, detect drift and artificial stops, verify before done"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
authors = [{ name = "Ryan Wesley Hitchcock" }]
|
|
13
|
+
keywords = ["execution", "task-management", "state-machine", "agents", "workflow"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Operating System :: OS Independent",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
[project.urls]
|
|
21
|
+
Homepage = "https://github.com/ohpersonperson/frontface"
|
|
22
|
+
|
|
23
|
+
[tool.setuptools.packages.find]
|
|
24
|
+
where = ["src"]
|
|
25
|
+
|
|
26
|
+
[tool.pytest.ini_options]
|
|
27
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""flashy — front-facing conversion of the flashy execution skill.
|
|
2
|
+
|
|
3
|
+
Persistent execution discipline for long tasks: lock the objective,
|
|
4
|
+
expand it into milestones, track live state, detect drift and artificial
|
|
5
|
+
stopping points, checkpoint honestly on genuine limits, and gate
|
|
6
|
+
completion on a quality checklist. A draft is not a delivery.
|
|
7
|
+
|
|
8
|
+
No prerequisites. The mechanism is the whole product.
|
|
9
|
+
See README.md for the plain-language version. See CHANGELOG.md for what
|
|
10
|
+
was decoupled from the original skill.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from . import checkpoints, detectors, quality_gate, session, state_machine
|
|
14
|
+
from .checkpoints import (
|
|
15
|
+
Checkpoint, CheckpointError, make_resume_token, parse_resume_token,
|
|
16
|
+
)
|
|
17
|
+
from .detectors import (
|
|
18
|
+
ARTIFICIAL_STOP_PHRASES, PERMISSION_PHRASES, DRIFT_QUESTIONS,
|
|
19
|
+
DRIFT_CORRECTION, HIGH, MEDIUM, LOW, CONFIDENCE_LEVELS,
|
|
20
|
+
CONFIDENCE_GUIDANCE, DetectorError, detect_artificial_stop,
|
|
21
|
+
asks_permission_to_continue, should_pause, is_implementation_changing,
|
|
22
|
+
)
|
|
23
|
+
from .quality_gate import QualityGate, GateError, GATE_ITEMS
|
|
24
|
+
from .session import MissionSession, Milestone
|
|
25
|
+
from .state_machine import (
|
|
26
|
+
ExecutionStateMachine, TransitionError,
|
|
27
|
+
LOCKED, PLANNING, EXECUTING, VERIFYING, DONE, BLOCKED,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
__version__ = "1.0.0"
|
|
31
|
+
__all__ = [
|
|
32
|
+
"checkpoints", "detectors", "quality_gate", "session", "state_machine",
|
|
33
|
+
"Checkpoint", "CheckpointError", "make_resume_token", "parse_resume_token",
|
|
34
|
+
"ARTIFICIAL_STOP_PHRASES", "PERMISSION_PHRASES", "DRIFT_QUESTIONS",
|
|
35
|
+
"DRIFT_CORRECTION", "HIGH", "MEDIUM", "LOW", "CONFIDENCE_LEVELS",
|
|
36
|
+
"CONFIDENCE_GUIDANCE", "DetectorError", "detect_artificial_stop",
|
|
37
|
+
"asks_permission_to_continue", "should_pause", "is_implementation_changing",
|
|
38
|
+
"QualityGate", "GateError", "GATE_ITEMS",
|
|
39
|
+
"MissionSession", "Milestone",
|
|
40
|
+
"ExecutionStateMachine", "TransitionError",
|
|
41
|
+
"LOCKED", "PLANNING", "EXECUTING", "VERIFYING", "DONE", "BLOCKED",
|
|
42
|
+
]
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"""Checkpoints and resume tokens: honest "not done yet" state.
|
|
2
|
+
|
|
3
|
+
A checkpoint is emitted only on a genuine limit — a context/length limit,
|
|
4
|
+
a session or tool interruption, or a low-confidence blocker with no safe
|
|
5
|
+
default. Never on fatigue, never on "this section feels done," never as a
|
|
6
|
+
soft way to stop early.
|
|
7
|
+
|
|
8
|
+
The resume token is a short, copy-pasteable pointer, not a cryptographic
|
|
9
|
+
token: `finish:<slug>-m<N>` (package:slug-milestoneIndex). Quoting it on
|
|
10
|
+
resume must let the session reconstruct where things stood from the
|
|
11
|
+
checkpoint block alone.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import hashlib
|
|
17
|
+
import json
|
|
18
|
+
import re
|
|
19
|
+
from dataclasses import dataclass, field
|
|
20
|
+
|
|
21
|
+
TOKEN_RE = re.compile(r"^finish:([a-z0-9][a-z0-9-]*)-m(\d+)$")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class CheckpointError(ValueError):
|
|
25
|
+
"""A malformed checkpoint or resume token."""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def make_resume_token(slug: str, milestone_index: int) -> str:
|
|
29
|
+
"""Build a resume token like `finish:debate-sim-m3`."""
|
|
30
|
+
slug = slug.strip().lower().replace(" ", "-")
|
|
31
|
+
if not slug or milestone_index < 0:
|
|
32
|
+
raise CheckpointError("Resume token needs a slug and a non-negative index.")
|
|
33
|
+
return f"finish:{slug}-m{milestone_index}"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def parse_resume_token(token: str) -> tuple[str, int]:
|
|
37
|
+
"""Split a resume token into (slug, milestone_index). Validates format."""
|
|
38
|
+
m = TOKEN_RE.match(token.strip())
|
|
39
|
+
if not m:
|
|
40
|
+
raise CheckpointError(
|
|
41
|
+
f"Malformed resume token: {token!r}. Expected finish:<slug>-m<N>."
|
|
42
|
+
)
|
|
43
|
+
return m.group(1), int(m.group(2))
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass
|
|
47
|
+
class Checkpoint:
|
|
48
|
+
objective: str
|
|
49
|
+
done: list[str] = field(default_factory=list)
|
|
50
|
+
current: str = ""
|
|
51
|
+
remaining: list[str] = field(default_factory=list)
|
|
52
|
+
next_action: str = ""
|
|
53
|
+
resume_token: str = ""
|
|
54
|
+
blockers: list[str] = field(default_factory=list)
|
|
55
|
+
# PARAM-FLASHY-004: SHA-256 binding over the content fields, stamped at
|
|
56
|
+
# write time. resume() verifies it; a mismatch aborts the resume.
|
|
57
|
+
digest: str = ""
|
|
58
|
+
# Set only via resume(allow_manual_edit=True): the operator explicitly
|
|
59
|
+
# acknowledged a hand edit. Rendered into the block so the override is
|
|
60
|
+
# visible, never silent. Excluded from the digest input.
|
|
61
|
+
manual_edit_override: bool = False
|
|
62
|
+
|
|
63
|
+
def __post_init__(self) -> None:
|
|
64
|
+
if not self.objective.strip():
|
|
65
|
+
raise CheckpointError("A checkpoint needs the locked objective.")
|
|
66
|
+
if not self.resume_token.strip():
|
|
67
|
+
raise CheckpointError("A checkpoint needs a resume token.")
|
|
68
|
+
parse_resume_token(self.resume_token) # validate format now
|
|
69
|
+
if not self.digest:
|
|
70
|
+
self.digest = self._compute_digest()
|
|
71
|
+
|
|
72
|
+
def _compute_digest(self) -> str:
|
|
73
|
+
"""SHA-256 over the canonical content payload.
|
|
74
|
+
|
|
75
|
+
Covers objective, done-list, and core state. The resume token is a
|
|
76
|
+
pointer, not content; the digest itself and the override flag are
|
|
77
|
+
resume-act metadata — all three are excluded.
|
|
78
|
+
"""
|
|
79
|
+
payload = json.dumps(
|
|
80
|
+
{
|
|
81
|
+
"objective": self.objective,
|
|
82
|
+
"done": list(self.done),
|
|
83
|
+
"current": self.current,
|
|
84
|
+
"remaining": list(self.remaining),
|
|
85
|
+
"next_action": self.next_action,
|
|
86
|
+
"blockers": list(self.blockers),
|
|
87
|
+
},
|
|
88
|
+
sort_keys=True,
|
|
89
|
+
)
|
|
90
|
+
return hashlib.sha256(payload.encode("utf-8")).hexdigest()
|
|
91
|
+
|
|
92
|
+
def verify(self) -> None:
|
|
93
|
+
"""Verify the checkpoint's content against its digest binding.
|
|
94
|
+
|
|
95
|
+
Raises CheckpointError on a missing digest (predates binding) or a
|
|
96
|
+
mismatch (content modified after the checkpoint was written).
|
|
97
|
+
"""
|
|
98
|
+
if not self.digest:
|
|
99
|
+
raise CheckpointError(
|
|
100
|
+
"Checkpoint carries no digest — it predates digest binding "
|
|
101
|
+
"and cannot be integrity-verified. Re-emit the checkpoint "
|
|
102
|
+
"to bind it."
|
|
103
|
+
)
|
|
104
|
+
if self.digest != self._compute_digest():
|
|
105
|
+
raise CheckpointError(
|
|
106
|
+
"Checkpoint digest mismatch: the content was modified after "
|
|
107
|
+
"the checkpoint was written. Resume aborted — re-verify the "
|
|
108
|
+
"checkpoint, or resume with allow_manual_edit=True to "
|
|
109
|
+
"acknowledge the hand edit explicitly."
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
@classmethod
|
|
113
|
+
def resume(cls, text: str, *, allow_manual_edit: bool = False) -> "Checkpoint":
|
|
114
|
+
"""Parse a checkpoint block and verify its digest binding.
|
|
115
|
+
|
|
116
|
+
Returns the reconstructed Checkpoint. On digest mismatch the resume
|
|
117
|
+
aborts with CheckpointError — unless allow_manual_edit=True, in
|
|
118
|
+
which case the checkpoint is returned with manual_edit_override set
|
|
119
|
+
(the override is rendered into the block, never silent).
|
|
120
|
+
"""
|
|
121
|
+
cp = cls.parse(text)
|
|
122
|
+
if not cp.digest:
|
|
123
|
+
# Pre-binding block: no write-time attestation exists. The
|
|
124
|
+
# bypass is for acknowledged hand edits (digest mismatch), not
|
|
125
|
+
# for unverifiable provenance — refuse outright, no override.
|
|
126
|
+
raise CheckpointError(
|
|
127
|
+
"Checkpoint carries no digest — it predates digest binding "
|
|
128
|
+
"and cannot be integrity-verified. Re-emit the checkpoint "
|
|
129
|
+
"to bind it."
|
|
130
|
+
)
|
|
131
|
+
try:
|
|
132
|
+
cp.verify()
|
|
133
|
+
except CheckpointError:
|
|
134
|
+
if not allow_manual_edit:
|
|
135
|
+
raise
|
|
136
|
+
cp.manual_edit_override = True
|
|
137
|
+
return cp
|
|
138
|
+
|
|
139
|
+
def render(self) -> str:
|
|
140
|
+
lines = [
|
|
141
|
+
"FLASHY CHECKPOINT",
|
|
142
|
+
f"OBJECTIVE -> {self.objective}",
|
|
143
|
+
f"DONE -> {', '.join(self.done) if self.done else '(none yet)'}",
|
|
144
|
+
f"CURRENT -> {self.current or '(not started)'}",
|
|
145
|
+
f"REMAINING -> {', '.join(self.remaining) if self.remaining else '(none)'}",
|
|
146
|
+
f"NEXT -> {self.next_action or '(none recorded)'}",
|
|
147
|
+
f"RESUME -> {self.resume_token}",
|
|
148
|
+
f"DIGEST -> {self.digest}",
|
|
149
|
+
]
|
|
150
|
+
if self.blockers:
|
|
151
|
+
lines.append(f"BLOCKERS -> {', '.join(self.blockers)}")
|
|
152
|
+
if self.manual_edit_override:
|
|
153
|
+
lines.append("OVERRIDE -> manual edit acknowledged by operator")
|
|
154
|
+
lines.append("")
|
|
155
|
+
lines.append("Continuing...")
|
|
156
|
+
return "\n".join(lines)
|
|
157
|
+
|
|
158
|
+
@classmethod
|
|
159
|
+
def parse(cls, text: str) -> "Checkpoint":
|
|
160
|
+
"""Parse a rendered checkpoint back. Raises CheckpointError on bad input."""
|
|
161
|
+
if "FLASHY CHECKPOINT" not in text:
|
|
162
|
+
raise CheckpointError("Not a Flashy checkpoint.")
|
|
163
|
+
fields: dict[str, str] = {}
|
|
164
|
+
for line in text.splitlines():
|
|
165
|
+
if "->" in line:
|
|
166
|
+
key, _, value = line.partition("->")
|
|
167
|
+
fields[key.strip()] = value.strip()
|
|
168
|
+
|
|
169
|
+
def split_list(value: str) -> list[str]:
|
|
170
|
+
value = value.strip()
|
|
171
|
+
if not value or value in ("(none yet)", "(none)"):
|
|
172
|
+
return []
|
|
173
|
+
return [p.strip() for p in value.split(",") if p.strip()]
|
|
174
|
+
|
|
175
|
+
try:
|
|
176
|
+
cp = cls(
|
|
177
|
+
objective=fields["OBJECTIVE"],
|
|
178
|
+
done=split_list(fields.get("DONE", "")),
|
|
179
|
+
current="" if fields.get("CURRENT") == "(not started)" else fields.get("CURRENT", ""),
|
|
180
|
+
remaining=split_list(fields.get("REMAINING", "")),
|
|
181
|
+
next_action="" if fields.get("NEXT") == "(none recorded)" else fields.get("NEXT", ""),
|
|
182
|
+
resume_token=fields["RESUME"],
|
|
183
|
+
blockers=split_list(fields.get("BLOCKERS", "")),
|
|
184
|
+
digest=fields.get("DIGEST", ""),
|
|
185
|
+
manual_edit_override="OVERRIDE" in fields,
|
|
186
|
+
)
|
|
187
|
+
except KeyError as exc:
|
|
188
|
+
raise CheckpointError(f"Checkpoint missing field: {exc}") from exc
|
|
189
|
+
if not fields.get("DIGEST"):
|
|
190
|
+
# Pre-binding block: __post_init__ computed a fresh digest, but
|
|
191
|
+
# no write-time attestation exists — clear it so verify()
|
|
192
|
+
# refuses explicitly instead of blessing content that was never
|
|
193
|
+
# bound.
|
|
194
|
+
cp.digest = ""
|
|
195
|
+
return cp
|