Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 34 additions & 11 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -596,6 +596,7 @@ timeout.
| `messages` | canonical inbound and outbound content, generation state, delivery state, chunk checkpoint |
| `approval_questions` | retained durable question and answer state |
| `job_runs` | immutable job claims, bounded results, evaluation, scheduling, and delivery |
| `job_schedule_reviews` and `job_schedule_events` | exact-revision schedule proposals, decisions, activation, and history |
| `gateway_control_actions` | idempotent control actions such as `/stop` targets |
| `channel_cursors` | monotonic per-channel polling checkpoints |
| `backend_sessions` | current backend session for each channel and thread |
Expand All @@ -609,9 +610,12 @@ Important constraints:
- scheduled occurrence identity is unique;
- control actions are idempotent by inbound message.

The approval tables and inbound answer path remain durable, but current
production job creation writes runbooks directly and does not create approval
questions.
Job files are authored directly. Enabled schedule activation uses the durable
question path and a separate activation ledger. A question is bound to one
allowlisted channel identity and the validated content revision, file identity,
effective backend, timeout, work directory, enabled triggers, and delivery
target. Answer selection and the schedule decision are recoverable across a
crash, and the scheduler still revalidates the exact revision before planning.

### Slack Inbox

Expand All @@ -627,6 +631,12 @@ Events include message metadata, routing, backend starts and failures, answer
outcomes, delivery results, and completion. Content is omitted by default.
`audit_log_content = true` opts into message and reply text.

Schedule lifecycle events first enter `job_schedule_events` as a SQLite
outbox. The gateway syncs each JSONL append before acknowledging it in SQLite
and retries pending rows on startup and scheduler or conversation activity.
Replay is at least once, so a crash between append and acknowledgement may
duplicate the stable schedule `event_id`.

---

## 8. Job System
Expand Down Expand Up @@ -683,20 +693,33 @@ The gateway ticks the scheduler once per second:
```text
1. RECOVER inspect stale runs and delivery claims
2. CATALOG reload and validate installed jobs
3. PLAN calculate next cron occurrence in its IANA timezone
4. ENQUEUE record one due occurrence in push.db
5. CLAIM take work up to jobs_max_workers
6. EXECUTE run a fresh unattended backend session
7. EVALUATE optionally run the restricted evaluator
8. STORE commit result, error, and evaluation state
9. CLAIM SEND take due delivery work across gateway processes
10. DELIVER send stored chunks and checkpoint progress
3. REVIEW reconcile the exact revision and require durable activation
4. PLAN calculate next cron occurrence in its IANA timezone
5. ENQUEUE record one due occurrence in push.db
6. CLAIM take work up to jobs_max_workers
7. EXECUTE run a fresh unattended backend session
8. EVALUATE optionally run the restricted evaluator
9. STORE commit result, error, and evaluation state
10. CLAIM SEND take due delivery work across gateway processes
11. DELIVER send stored chunks and checkpoint progress
```

Push does not catch up occurrences missed while offline. A clock jump queues at
most one occurrence, daylight-saving gaps are skipped, and repeated local
times run once at their first instant.

Direct Markdown authoring, validation, inspection, disabled triggers, and
manual runs do not require schedule activation. A new or changed enabled
revision is proposed but omitted from planning until the exact owner-bound
review is approved. Any later validation failure, content change, path or
symlink replacement, or change to effective execution or delivery settings
invalidates it. A version-11 database migration activates only valid enabled
schedules whose exact revisions are captured by the first config-aware open
after upgrade, so upgrades preserve existing intended recurrence without
creating a later grandfathering window. When that capture has no valid primary
destination, the migration records an empty baseline and closes without
activating schedules.

### Execution and Delivery Semantics

- A failed or timed-out job is not rerun because the agent may already have
Expand Down
10 changes: 7 additions & 3 deletions assistant/skills/push/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ description: Operate a Push personal assistant, inspect its health and jobs, and
license: MIT
compatibility: Requires the Push CLI and an initialized assistant repository.
metadata:
push-managed-version: "1"
push-managed-version: "2"
---

# Push
Expand Down Expand Up @@ -43,6 +43,7 @@ are:
- `push job show <name>`
- `push job run <name>`
- `push job runs [<name>]`
- `push job reviews [<name>]`

All commands accept `--config <path>`. Do not assume machine-readable output
unless `push help` documents it in the installed version. Never expose tokens,
Expand All @@ -57,9 +58,12 @@ message content, or sensitive runtime state in diagnostics or replies.
environment for credentials.
4. Run `push job validate` after every job change. Do not claim success if
validation fails.
5. Use `push job show <name>` to inspect the installed result and
5. Saving a new or changed enabled schedule does not activate it. Tell the user
that Push will present the exact revision for separate owner review.
6. Use `push job show <name>` to inspect the installed result,
`push job reviews <name>` to inspect schedule activation state, and
`push job runs <name>` to inspect execution and delivery history.
6. Run `push job run <name>` only when the user asked for the job to execute or
7. Run `push job run <name>` only when the user asked for the job to execute or
when execution is a clearly authorized part of the task.

## Reply normally
Expand Down
2 changes: 1 addition & 1 deletion docs/core-system/design.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ Jobs: /resolved/path/to/assistant/jobs

Begin with context/README.md when user context is relevant.
Do not modify SOUL.md or evals unless the user asks.
When the user asks to create or change a job, write the complete runbook directly under Jobs and run `push job validate` before saying it succeeded.
When the user asks to create or change a job, write the complete runbook directly under Jobs and run `push job validate` before saying it was saved. A new or changed enabled schedule remains inactive until Push presents its exact revision for owner review.
```

Claude Code and Pi receive the composed text as appended system instructions.
Expand Down
4 changes: 2 additions & 2 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ sends the result back when the work is done.
<div class="push-chat-bar"><span>Telegram</span><span><i></i> Push is online</span></div>
<div class="push-chat-body">
<div class="push-chat-message push-chat-message--user"><span>You · 18:12</span><p>Every weekday at 8am, run my morning brief and send me the three things that need my attention.</p></div>
<div class="push-chat-status"><span>Push → Codex</span><span>Draft ready for approval</span></div>
<div class="push-chat-message push-chat-message--assistant"><span>Push · 18:14</span><p>I drafted your morning brief for weekdays at 8am. Approve it to start the schedule.</p></div>
<div class="push-chat-status"><span>Push → Codex</span><span>Schedule ready for review</span></div>
<div class="push-chat-message push-chat-message--assistant"><span>Push · 18:14</span><p>I saved your morning brief for weekdays at 8am. Approve this exact revision to start the schedule.</p></div>
</div>
<div class="push-chat-footer"><span>Delivered in chat</span><span>Conversation saved</span></div>
</div>
Expand Down
53 changes: 44 additions & 9 deletions docs/jobs.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ push job show repo-review
```

Validation reports every valid and invalid file. An invalid job is disabled
individually and does not stop messaging or other valid jobs.
individually and does not stop messaging or other valid jobs. Validation does
not activate an enabled schedule.

## Evaluate completed work

Expand Down Expand Up @@ -140,6 +141,30 @@ Scheduling starts only when the primary destination is enabled and
allowlisted. A missing or invalid destination disables new scheduled starts
without affecting conversations or manual jobs.

Saving `enabled = true` creates a schedule activation proposal. The Markdown
file remains available for validation, inspection, and `push job run`, but the
scheduler does not plan the enabled trigger until the proposal is approved.
Push assigns each proposal to one allowlisted conversation when it presents a
durable question showing the exact job name, content revision, enabled cron
schedules, effective backend, timeout, work directory, and primary delivery
target. That persisted identity is the review owner; concurrent conversations
cannot adopt or answer its question. Reply with the question UUID followed by
the number for Approve or Reject. A number alone is rejected so a delayed reply
cannot select a replacement question. The question expires after 24 hours.

Approval is bound to the exact channel, sender, chat, thread or topic, validated
file revision, file identity, effective execution settings, and delivery
target. A file edit, invalid file, symlink or path replacement, schedule change,
backend change, timeout change, work-directory change, or delivery-target
change invalidates the prior activation before it can run. A later valid
revision receives a new review. Disabled triggers and jobs without triggers do
not require activation review.

Use `push job reviews [<name>]` to inspect current and historical schedule
review state. A manually edited schedule is still detected and kept inactive;
the next completed request from an allowlisted conversation can receive its
review question.

Push runs at most `jobs_max_workers` scheduled jobs concurrently. It does not
catch up cron occurrences missed while offline. Daylight-saving gaps are
skipped; repeated local times run once at their first instant. Cron expressions
Expand Down Expand Up @@ -189,20 +214,30 @@ delivery attempts, destination, bounded results, and error details.

When a user asks for a job, the assistant writes the complete runbook directly
to `<assistant_root>/jobs/<lowercase-slug>.md` and runs `push job validate`.
There is no separate draft or approval step. The selected agent's filesystem
permissions control whether it can change the assistant repository.
There is no separate draft-file or installation approval step. The selected
agent's filesystem permissions control whether it can change the assistant
repository. A new or changed enabled schedule remains inactive until its
separate activation review succeeds.

For an assistant repository created before this change, replace any `AGENTS.md`
instruction that says to propose jobs through approval with the direct-write
rule above. The gateway's runtime instruction overrides that old rule, but
updating the repository keeps its checked-in guidance accurate.

Pending job approvals from older Push versions are cancelled during database
migration. Replying to one explains that the job must be requested again.
Pending draft-install approvals from older Push versions are cancelled during
database migration. Replying to one explains that the job must be requested
again. On upgrade to schedule activation review, each valid enabled schedule
whose exact revision exists at the first config-aware Push command is captured
as the migration baseline. Those revisions are recorded as approved and
activated once when a valid primary destination exists. If that first command
has no valid primary destination, Push records an empty baseline and closes the
migration without grandfathering any schedules. Disabled and invalid jobs are
not grandfathered.

!!! warning

Jobs have no interactive approval path. Push runs Codex jobs with full
access and no prompts and Claude jobs in `bypassPermissions` mode. Treat
every enabled job as code execution by the Push service user, review
changes to the assistant repository, and allow only trusted senders.
Schedule activation review is not an agent permission prompt. After
activation, Push runs Codex jobs with full access and no prompts and Claude
jobs in `bypassPermissions` mode. Treat every activated job as code
execution by the Push service user, review changes to the assistant
repository, and allow only trusted senders.
11 changes: 7 additions & 4 deletions docs/jobs/design.md
Original file line number Diff line number Diff line change
Expand Up @@ -256,10 +256,13 @@ execution:
- `push job run <name>`
- `push job runs [<name>]`

Push is the only writer to the run ledger. The CLI owns the manual run it
claims, and the gateway owns scheduled runs. Agents may write requested job
files directly under `<assistant_root>/jobs` when their filesystem permissions
allow it, then validate the catalog with `push job validate`.
Push is the only writer to the run and schedule-activation ledgers. The CLI
owns the manual run it claims, and the gateway owns scheduled runs. Agents may
write requested job files directly under `<assistant_root>/jobs` when their
filesystem permissions allow it, then validate the catalog with
`push job validate`. Direct authoring does not activate a new or changed enabled
schedule. The gateway binds owner review to the exact validated revision and
effective execution and delivery settings before planning it.

## Alternatives and tradeoffs

Expand Down
35 changes: 34 additions & 1 deletion docs/reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ root.
| `push job show <name>` | Print the parsed installed job |
| `push job run <name>` | Claim and run one job in the CLI process |
| `push job runs [<name>]` | Print run and delivery history, optionally for one job |
| `push job reviews [<name>]` | Print schedule activation review state and exact revision metadata |

Examples:

Expand All @@ -36,13 +37,21 @@ push reload
push job validate
push job run repo-review
push job runs repo-review
push job reviews repo-review
```

Unknown commands and missing values fail with the accepted command forms. The
CLI does not currently provide shell completion or separate help pages for
subcommands. A `--help` flag anywhere in the argument list prints the global
help shown by `push --help`.

`push job validate`, `list`, and `show` never activate a schedule. Use
`push job reviews [<name>]` to inspect proposed, approved, rejected,
invalidated, and activated revisions, including their schedules, effective
backend, timeout, work directory, and delivery target. Activation decisions
are made by replying to the durable review question from the exact persisted
allowlisted channel identity that received it.

`push reload` and its `push restart` alias target the service definitions documented by Push:
`com.owainlewis.push` under launchd on macOS and the `push.service` user unit
under systemd on Linux. The service definition controls its config path,
Expand Down Expand Up @@ -72,7 +81,7 @@ available for:

- `help` and `version`
- `doctor`, `status`, and `paths`
- `job validate`, `job list`, `job show`, and `job runs`
- `job validate`, `job list`, `job show`, `job runs`, and `job reviews`

Commands that start or mutate runtime state reject `--json`. This includes the
gateway, `init`, `reload`, `restart`, and `job run`. In particular, Push does
Expand Down Expand Up @@ -256,6 +265,30 @@ The run projection queries only `job_runs` from the shared SQLite database. It
does not include co-located channel cursors, backend session IDs, conversation
messages, stored job output, evaluation text, or error text.

`job reviews` data:

| Field | Type | Values |
| --- | --- | --- |
| `job_name` | string or null | Requested job filter, or null for all jobs |
| `reviews` | array of review objects | Up to 100 newest stored schedule review revisions |
| `reviews[].review_id` | string | Exact activation fingerprint |
| `reviews[].job_name` | string | Job slug |
| `reviews[].status` | string enum | `proposed`, `approved`, `rejected`, `invalidated`, or `activated` |
| `reviews[].content_hash` | string | Authored Markdown SHA-256 |
| `reviews[].schedules` | array of trigger objects | Enabled triggers bound to the review |
| `reviews[].schedules[].id` | string | Trigger slug |
| `reviews[].schedules[].kind` | string constant | `cron` |
| `reviews[].schedules[].schedule` | string | Five-field cron expression |
| `reviews[].schedules[].timezone` | string | IANA timezone name |
| `reviews[].schedules[].enabled` | boolean | Always `true` for a reviewed trigger |
| `reviews[].backend` | string enum | Effective `claude`, `codex`, or `pi` backend |
| `reviews[].timeout_ms` | integer | Effective timeout in milliseconds |
| `reviews[].workdir` | string | Resolved backend working directory |
| `reviews[].delivery.channel` | string | Bound delivery channel |
| `reviews[].delivery.target` | string | Bound delivery target |
| `reviews[].reviewed_by` | string or null | Bound actor for a decided revision |
| `reviews[].reason` | string or null | Invalidation or migration reason |

Fields may be added compatibly within version 1. Existing fields, meanings,
category names, and types will not change without a schema-version change.

Expand Down
29 changes: 20 additions & 9 deletions docs/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,14 @@ runs Claude jobs in `bypassPermissions` mode. Pi already has no native
filesystem sandbox or interactive permission prompt. Evaluators remain
read-only with tools disabled.

This makes job bodies equivalent to unattended code execution as the Push
service user. The assistant repository is the default work directory. An
explicit work directory must exist. Push rejects overlap with runtime state and
a loaded config stored outside the assistant repository. Keep allowed senders
and job definitions trusted, and run the service with only the OS permissions
its jobs require.
This makes activated job bodies equivalent to unattended code execution as the
Push service user. New and changed enabled schedules stay inactive until an
allowlisted owner approves the exact revision and effective execution settings.
The assistant repository is the default work directory. An explicit work
directory must exist. Push rejects overlap with runtime state and a loaded
config stored outside the assistant repository. Keep allowed senders and job
definitions trusted, and run the service with only the OS permissions its jobs
require.

Do not place secrets in a job body. Make them available through the backend or
service environment using the narrowest policy that works.
Expand Down Expand Up @@ -96,9 +98,13 @@ This reduces exposure, but it does not make an allowed message harmless.

Bounded `ask_user` questions are stored before delivery, survive restart,
expire, and can be consumed once. Mismatched, duplicate, ambiguous, cancelled,
and expired answers do not reach an agent. Job creation does not use this
mechanism. The selected agent's filesystem permissions control access to jobs
in the assistant repository.
and expired answers do not reach an agent. Direct job-file creation does not
use this mechanism. Enabled schedule activation does: the review binds the
exact allowlisted channel identity to the content hash, file identity,
validated schedules, effective backend, timeout, work directory, and delivery
target. Revalidation and scheduler claims fail closed when those values no
longer match. Schedule reviews require the question UUID with the answer
number; uncorrelated numbers cannot approve a replacement question.

## Audit log

Expand All @@ -107,6 +113,11 @@ include metadata such as row ID, channel, thread, backend, decision, target,
error, and character counts. Message and reply content are omitted unless
`audit_log_content = true`.

Schedule lifecycle events use a durable SQLite outbox. Push syncs each JSONL
event before marking it delivered and retries pending events after a write
failure or restart. A crash after append but before acknowledgement can produce
a duplicate with the same `event_id`; consumers should deduplicate that ID.

The redacted log is still sensitive because it can contain handles, thread
IDs, file paths, and backend errors. Protect and rotate it like a service log.

Expand Down
Loading
Loading