Sennight is a small, copyable weekly planning system. A scheduled agent reads messages, updates repository state, regenerates a weekly plan, validates it, sends a digest, and commits the result. Keep the implementation readable in one sitting. Prefer direct data files and deterministic checks over services, frameworks, databases, or speculative abstractions.
This repository contains two distinct surfaces:
index.htmlandCNAMEare the public landing page atsennight.agentlab.in. They explain the project and link to the example.template/is the complete copyable planning system. Users move its contents to the root of their own private repository.README.mddocuments both surfaces and the setup flow.
Do not confuse the landing page with the planner board:
- Root
index.htmlis marketing and documentation. It must not read planner state or acquire runtime dependencies. template/index.htmlis the planner board. It readsplan.json,history.json, and optionalactivities/<id>.jsonfiles directly in the browser. It has no build step.- Paths inside
template/must remain valid aftertemplate/*is moved to a new repository root. Do not couple them to the enclosing Sennight checkout. - A landing page change normally stays at the repository root. A planner,
schema, state, workflow, or board change normally stays under
template/. - If user-facing claims about the template change, keep
README.mdand the root landing page accurate. Do not mechanically duplicate implementation details unless both surfaces already present them.
template/CLAUDE.mdis the runtime specification and ordered runbook for the scheduled planning agent.template/config.yamlis permanent user configuration: waking span, planning factor, catch-up cap, fixed anchors, activities, quotas, priorities, and activity-specific constraints.template/overrides.yamlcontains deviations for one ISO week. Spoken changes belong here unless the user explicitly sayspermanentlyorfrom now on.template/tasks.yamlcontains one-off deadline tasks.template/activities/<id>.jsonoptionally stores a locked, long-running program associated with an activity.template/logs/<ISO-week>.yamlis the append-only ledger of completed work.template/inbox/stores raw transport responses and transport cursors. It is the only message ingestion state.template/plan.jsonis generated state and the only plan consumed by the board.template/history.jsonis the append-only summary used for past weeks.template/questions.mdrecords interpretations or contract changes that need human resolution.template/schema/plan.schema.jsonis the frozen structural contract forplan.json.template/scripts/check_plan.pyis the deterministic semantic auditor.template/.github/workflows/plan.ymlis the six-hour scheduler and commit path.
When prose and executable behavior disagree, do not silently choose one. Preserve user data, identify the mismatch, and update the relevant contract and checks together only when the requested change authorizes it.
The planner has no database and no application server. Repository files are the complete state machine:
- The workflow starts on its cron schedule or through manual dispatch.
- The agent reads the newest cursor from the last inbox file.
- It fetches unread messages from each configured transport and writes one immutable raw inbox file for the run.
- It interprets messages into logs, tasks, weekly overrides, permanent configuration, preferences, or open questions.
- It computes effective configuration by deep-merging the current ISO week's override onto base configuration.
- It replans today through Sunday and rewrites
plan.jsonas a whole. - It runs
python3 scripts/check_plan.pyfromtemplate/and fixes every failure. - If anything changed, it sends the same compact digest through each configured transport.
- The workflow stages, commits, and pushes repository changes. The agent run itself stages changes but does not push.
Messages are processed forward. A newer message wins when messages conflict.
Never infer missing logged hours or fabricate completed work. Put ambiguous
input in questions.md.
Preserve these rules in every planner change:
- Logs are append-only. Correct a prior log with a new entry, including a negative-hour entry when needed. Never rewrite or delete an old entry.
- Inbox files are verbatim, permanent records. Never rewrite or delete them.
- Inbox files carry all fetch cursors. Do not introduce hidden cursor state.
history.jsonis append-only. At the first run of a new week, append the prior week's final tally. Never rewrite existing week entries.plan.jsonis regenerated in full. Never hand-patch a fragment as runtime behavior.- Every configured anchor must appear unchanged on its day. A cancellation
must be declared under that week's
anchors.skip. A one-off anchor must be declared underanchors.addbefore it appears in the plan. - Flexible work uses ordered duration blocks inside free windows. It does not receive invented clock times.
- Planned non-rest work must not exceed the effective
planning_factorof a free window. - Deadline tasks are allocated before recurring activities. Recurring activities are allocated by ascending priority number.
- Unmet quotas are represented honestly in
shortfalls; do not hide them by changing quota accounting or overfilling windows. - Activity IDs in plans and logs must match configured activity IDs. Deadline
tasks use
task:<id>.restis the only reserved non-activity block. activities/*.jsonprograms are locked. The runtime agent may only change a step'sdonevalue. It must not reorder, rename, annotate, add, or remove steps.- The plan schema is frozen during ordinary planning runs. Proposed schema
changes belong in
questions.md, not in an opportunistic schema edit. - A changed run must send one digest to every configured transport. The text must be the same for all transports.
- Discord ingestion must ignore messages sent by the bot itself.
- Missing transport secrets disable only that transport. They do not make the other transport or planning itself fail.
- ISO weeks begin on Monday.
plan.week_ofmust be that week's Monday. - A normal run plans only the remaining dates from today through Sunday.
- Free windows fit inside
planning.day_startandplanning.day_end, do not overlap anchors, and do not overlap one another. - Overnight waking spans are represented by allowing configured end times
after midnight. The schema permits plan window endings through
24:30. - Block order values must be unique and strictly increasing within a window.
- Logged plus planned hours determine expected quota completion.
- Task work counts only when logged or planned no later than its deadline.
max_catchup_per_dayapplies per recurring activity per day.- DSA weekend minimums include Saturday and Sunday logs plus planned blocks.
- Gym rules include required days, evening placement, and the configured maximum sessions per day.
intern_flexmust be placed adjacent to its configured anchor. The auditor treats a window as adjacent when its edge is within 30 minutes of the anchor edge.- Prefer explicit shortfalls over unsafe compression or front-loaded catch-up.
Run the validator from template/:
python3 scripts/check_plan.pyInstall its only Python dependencies when needed:
python3 -m pip install pyyaml jsonschemaAn optional historical week can be selected with:
python3 scripts/check_plan.py --week 2026-W31The validator checks JSON Schema compliance, Monday week boundaries, weekday and date agreement, anchor preservation, free-window bounds and overlap, planning-factor capacity, block ordering, known activity IDs, daily catch-up caps, quota arithmetic, declared shortfalls, the DSA weekend minimum, parts of the gym rules, intern adjacency, and deadline task placement.
The validator is necessary but not sufficient. Review manually for rules it does not fully prove, including:
- correct message interpretation and newest-message precedence;
- raw inbox preservation and correct transport cursor advancement;
- deep-merge behavior for nested overrides;
- allocation priority and sensible spreading across remaining days;
- exact gym session length and weekday session count;
- activity-specific allowed days not explicitly audited;
- truthful shortfall reasons and
expected_final_hvalues; - digest delivery, identical digest text, and self-message filtering;
- locked activity fields other than
done; - correct weekly history rollover.
If a new invariant matters, add a deterministic validator check when practical and update the runtime specification. Do not claim the current auditor checks something it does not.
For landing page work:
- Edit root
index.htmland, only when domain routing changes,CNAME. - Preserve the static, dependency-free page and the link to
./template/. - Check links, responsive layout, and claims against the current template.
For board work:
- Edit
template/index.htmlwithout adding a build requirement. - Preserve graceful handling when
plan.json,history.json, or an optional activity file cannot be loaded. - Treat the schema as the rendering contract. Coordinate intentional contract changes across schema, sample data, renderer, validator, runtime spec, and documentation.
For planner behavior:
- Change
template/CLAUDE.mdand the auditor together when an invariant or run step changes. - Keep the run deterministic where possible. Do not move enforceable rules into prose alone.
- Preserve the repository-only state model. Adding a database, daemon, or bespoke transport service is an architectural change, not routine cleanup.
For sample state:
- Remember that checked-in files demonstrate the product immediately after a fork.
- Keep sample config, overrides, logs, plan, history, and activities mutually coherent.
- Never treat fictional sample entries as the maintainer's real personal data.
The workflow may receive these repository secrets:
CLAUDE_CODE_OAUTH_TOKENTELEGRAM_BOT_TOKENTELEGRAM_CHAT_IDDISCORD_BOT_TOKENDISCORD_CHANNEL_ID
Never print, inspect, persist, commit, or copy secret values. Do not place tokens or identifiers into fixtures, examples, inbox files, logs, plan data, HTML, workflow output, screenshots, or issue text. Preserve empty-secret behavior so a fork can configure Telegram, Discord, both, or neither.
Raw inbox messages and life-planning state may be sensitive even when they do not contain credentials. Do not upload them, paste them into external tools, or expose them through the public landing page. The intended user repository is private unless its owner deliberately chooses otherwise.
The workflow has contents: write permission and can fetch private messages,
send messages, commit, and push. Treat those as real external side effects.
- Do not trigger or dispatch the workflow unless explicitly requested.
- Do not call Telegram, Discord, Claude, GitHub, Pages, DNS, or another remote service merely to test a local edit.
- Do not send a digest, fetch live messages, commit, push, publish, deploy, or
change
CNAMEunless the task explicitly authorizes that action. - Do not broaden workflow permissions or add secrets without explicit need.
- Keep
concurrency.group: plannerandcancel-in-progress: falseunless a requested scheduling change requires different semantics. Overlapping runs can corrupt cursor and append-only state. - Keep the workflow's responsibility split clear: the planning agent performs the standing run and stages changes; the final workflow step owns commit and push.
Use local inspection and the validator for ordinary verification. If a change cannot be verified without a live side effect, report that limitation instead of performing the side effect.
For every change under template/:
- Run
python3 scripts/check_plan.pyfromtemplate/. - Review all touched YAML and JSON for parseability and cross-file IDs.
- Manually check the relevant invariants not covered by the auditor.
- For board changes, serve
template/locally and inspect the board at desktop and narrow widths. - For workflow changes, review permissions, secret exposure, shell quoting, concurrency, and which step owns each external action.
For root landing page changes, validate the static page locally and verify the template link. There is no repository-wide build command.
At handoff, report exactly what changed, which checks passed, which checks failed, and which checks were skipped. Preserve unrelated worktree changes. Do not commit or push unless explicitly requested.