Conversation
β¦topping it Implements the Layer 6 foundations from #53 (ship-order steps 1+2): - questions.py: structured <question> protocol with an enforced quality contract β >=2 mutually exclusive options with one-line consequences, exactly one recommendation, mandatory no-answer default. Bare DECIDE:text payloads are synthesized into a safe low-fidelity binary. Questions persist as JSON under .owloop/questions/ (files first, transport second); answers become attributed per-spec steering under .owloop/steering/ that the next iteration loads into its prompt. - channels.py: AnswerChannel transports chosen for dev machines with no public endpoint. Telegram (stdlib urllib, getUpdates long-poll, inline-keyboard buttons, ack-by-edit, chat-id allowlist with silent drop) is bidirectional; ntfy.sh is notify-only. Night mode (quiet hours 22:00-08:00) renders every question as the recommendation-first binary so both possible answers are safe; day mode sends the full option list. Reply correlation ladder: button callback -> id in text -> bare key only when exactly one question is pending (never guess). - engine: DECIDE now raises a question, posts it to channels, and waits a bounded --question-wait for an answer; an answer un-wedges the run (the next iteration retries the spec with the decision as steering) instead of stopping the whole night. BLOCKED and run-completion send notifications. The inter-iteration sleep doubles as the reply long-poll. All channel I/O is best-effort: a dead transport costs delivery, never the loop. - CLI: owloop run --channels telegram,ntfy --question-wait N, with env validation (OWLOOP_TELEGRAM_BOT_TOKEN/CHAT_ID, OWLOOP_NTFY_TOPIC) before the loop starts. - 36 new tests: quality contract, rendering rules (options never truncated), correlation, allowlist drop, transport-failure tolerance, and engine integration incl. answered/skip/expired paths. Park-and-continue (one question costs one spec, not the night) is deliberately deferred until #33's terminal-state taxonomy lands.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements the Layer 6 foundations designed in #53 (ship-order steps 1 + 2: notify-first, then the reply poller). The loop can now push a
DECIDEquestion to your phone at 11 p.m., and your one-tap reply un-wedges the run instead of the whole night dying.What's in
questions.pyβ the structured question protocol<promise>DECIDE:β¦</promise>with a<question>{JSON}</question>block (title, context, options with one-line consequences, recommendation). The engine enriches it with mechanical facts it owns (spec, iteration, branch) β same trust boundary as feat(engine): deterministic verification gate β engine must own commit, push, and spec completionΒ #32.ensure_quality): β₯2 mutually exclusive options, exactly onerecommended, mandatory no-answer default, context/evidence budgets. A bare old-formatDECIDE:textis synthesized into the safe binary ("proceed as proposed" / "skip") and marked low-fidelity β never posted raw..owloop/questions/regardless of channel health; answers become attributed, fenced per-spec steering under.owloop/steering/<spec>.md, which the next iteration loads into its prompt (guidance data, never raw prompt splicing).channels.pyβ transports for a dev machine with no public endpointsendMessagewith inline-keyboard buttons (callback_datakept β€64 bytes),getUpdateslong-poll, ack-by-edit viaeditMessageText, chat-id allowlist with silent drop of everyone else./skipand/steer <text>verbs supported; first valid reply wins.Engine integration
DECIDEnow raises a question, posts it, and waits a bounded--question-wait(default 30 min) for an answer. Answered β the run continues: the next iteration retries the spec with the human's decision injected as steering (fresh-context makes resume free β the spec queue is the checkpointer). Skip/expiry β the previous stop-for-review behavior, with the full question preserved for the morning.BLOCKEDand run-completion send notifications; the inter-iteration sleep doubles as the reply long-poll.channel_errorevents, and the loop proceeds. A dead Telegram API costs delivery, never correctness (covered by an exploding-channel test).CLI
export OWLOOP_TELEGRAM_BOT_TOKEN=... OWLOOP_TELEGRAM_CHAT_ID=... owloop run --channels telegram --question-wait 30Env validation happens before the loop starts, not at 2 a.m.
Deliberately out of scope
AnswerChannelmatrix row is the extension point.Verification
uv run pytest -qβ 294 passed (36 new: quality contract, night/day rendering rules, correlation ladder, allowlist drop, Telegram transport against mocked urllib, engine answered/skip/expired/transport-failure paths)uv run ruff check src/owloop testsβ cleanuv run mypy src/owloop testsβ cleanCloses nothing yet; advances #53 (Phase III.5).
π€ Generated with Claude Code
https://claude.ai/code/session_016mrgfGebBKR3xGfywkqj3w
Generated by Claude Code