Next Action CLI & Advisory Provenance #24

Closed
opened 2026-09-22 17:58:57 +00:00 by yuris · 0 comments
Owner

Summary

Implement the user-facing CLI command life next and REST API endpoint POST /v1/next, providing structured, top-3 task recommendations with strictly separated factual constraints, semantic judgments, unknown attributes, and model provenance. Enforce the inviolable invariant that recommendations are strictly read-only and advisory, performing zero calendar writes.

Background & Technical Rationale

The life next command is the primary entry point for a user deciding what actionable task to focus on right now. It orchestrates the policy filter (#022), the multi-criteria lexicographic ranker (#023), and the Jev advisory scoring adapter (#021).

The interaction model enforces foundational architectural and usability invariants:

  1. Strictly Read-Only Advisory Contract:
    • Calling life next or POST /v1/next performs ZERO writes to Radicale or SQLite.
    • Selecting, reviewing, or viewing a recommended task is NOT task completion. Task completion requires an explicit, separate instruction (life task complete <ID> --etag <ETAG> --operation-id <OP_ID>).
    • Calendar state remains completely untouched during recommendation generation.
  2. Standard API Envelope & Provenance:
    • Responses use the canonical Life JSON envelope:
      {
        "schema_version": 1,
        "request_id": "req_...",
        "status": "ok",
        "data": { "candidates": [...] },
        "warnings": [],
        "error": null
      }
      
    • Returns at most three candidates.
    • Each candidate includes:
      • item_id: Authoritative task identifier (itm_...).
      • title: Task summary string.
      • deadline: Nullable tagged date or instant ({"kind": "date", "value": "2030-04-12"}).
      • factual_reasons: Array of objective constraints (e.g. "Fits 90m window", "Matches unmet weekday career floor").
      • semantic_reasons: Array of Jev score assessments (e.g. "alignment.v1 score 8.4/10: Strongly aligned with career goals").
      • unknowns: Array of missing attributes (e.g. ["estimated_minutes"]).
      • judgment_provenance: Evaluation metadata (model: "jev-1.13.0", evaluated_at: "<ISO8601>").
  3. Graceful Handling of Zero Eligible Tasks:
    • If all open tasks are excluded by the policy filter (e.g., all tasks exceed the window, or all require high energy), the endpoint returns status: "ok" with candidates: [].
    • It attaches diagnostic warnings explaining why candidates were filtered out (e.g., {"code": "no_eligible_tasks", "message": "All 8 active tasks exceed the 45m window duration"}).
  4. Human & Machine Interfaces:
    • Terminal Human View (default): Displays a clear, non-judgmental terminal card/table. Highlights factual reasons, deadline urgency, and unknown data warnings. Contains no guilt-driven messaging, behavioral lecturing, or fake urgency.
    • Machine JSON View (--json): Strict, structured JSON payload matching the API schema for consumption by AI agents or shell scripts.

Exact Technical References & Specifications

CLI & API Route Contract

From docs/technical/cli-api.md (lines 43, 57, 71, 90):

Command Method and route Scope
life next --window 90m --energy medium POST /v1/next read
  • Command Payload & Results:
    • Required / optional input: window as positive duration (e.g., 90m, 2h), energy (low | medium | high); optional area.
    • Response data: Up to three candidates with item_id, factual_reasons, semantic_reasons, unknowns, judgment_provenance.
    • Flags: CLI period/window/energy flags map to same-named query fields for GET and body fields for POST.
    • Common data types: Energy enum is low/medium/high. Temporal values follow tagged date/instant representations.
  • Error & Warning Identifiers (lines 108–127):
    • Warnings: judgment_unavailable, unknown_estimate, incomplete_calendar.

Workflow & Policy Defaults Contract

From docs/technical/workflows.md (lines 27, 44–50):

Workflow Input and reads Decision and approval Writes and observable result Failure path
Next task Available window and energy; live eligible tasks, deadlines and recorded qualifying outcomes Apply deterministic constraints before semantic ranking; policy order owned by Jev page At most three advisory choices, factual versus semantic reasons, no calendar writes Unknown duration/energy shown, not fabricated; Jev unavailable leaves explicit deterministic candidates, not claimed semantic ranking

"Policy exceptions are explicit, scoped and recorded with reason if supplied. No guilt-driven messaging, inferred completion, silent deletion or automatic idea promotion. Life-area categories are not diagnoses or rankings of human worth."

Verification Scenario W07

From docs/technical/verification.md (line 21):

ID Requirement and specification Input / action Observable acceptance
W07 Next action 90m, medium energy, deadlines and unmet career floor At most three eligible choices in documented order; factual/semantic reasons separated; unknown estimate not claimed to fit

Illustrative JSON Response Payload

{
  "schema_version": 1,
  "request_id": "req_demo_next_01",
  "status": "ok",
  "data": {
    "candidates": [
      {
        "item_id": "itm_career_portfolio",
        "title": "Draft fictional portfolio outline",
        "deadline": {
          "kind": "date",
          "value": "2030-04-12"
        },
        "factual_reasons": [
          "Matches unmet weekday career floor",
          "Estimated duration (45m) fits within available 90m window",
          "Required energy (medium) matches available energy"
        ],
        "semantic_reasons": [
          "alignment.v1 score 3.4/4: Highly aligned with active career outcomes",
          "readiness.v1 score 2.8/3: Clear next physical action identified"
        ],
        "unknowns": [],
        "judgment_provenance": {
          "model": "jev-1.13.0",
          "evaluated_at": "2030-04-08T06:30:00Z"
        }
      },
      {
        "item_id": "itm_code_review",
        "title": "Review team pull request",
        "deadline": null,
        "factual_reasons": [
          "Estimated duration unknown; not claimed to fit with certainty"
        ],
        "semantic_reasons": [
          "alignment.v1 score 2.5/4: Ongoing operational duty",
          "consequence.v1 score 2.0/4: Moderate consequence of delay"
        ],
        "unknowns": [
          "estimated_minutes"
        ],
        "judgment_provenance": {
          "model": "jev-1.13.0",
          "evaluated_at": "2030-04-08T06:30:00Z"
        }
      }
    ]
  },
  "warnings": [],
  "error": null
}

What to Build

1. src/api/routes/next.rs — Axum API Route

  • Request body struct:
    #[derive(Debug, Deserialize)]
    pub struct NextTaskApiRequest {
        pub window: String, // e.g. "90m", "2h"
        pub energy: EnergyLevel, // low, medium, high
        pub area: Option<String>,
    }
    
  • Duration parser converting human strings ("90m" \rightarrow 90, "2h" \rightarrow 120) into minutes. Rejects negative or unparseable durations as invalid_input.
  • Handler pipeline:
    1. Retrieve live schedule/agenda for the target time window to evaluate known busy time and schedule coverage. If calendar coverage is incomplete, attach warning incomplete_calendar and note in candidate unknowns that free capacity cannot be confirmed with certainty (docs/technical/jev.md:41).
    2. Retrieve live open VTODOs from CalendarClient.
    3. Query SQLite timeline for today's career floor attainment.
    4. Invoke filter_candidate_tasks (#022), enforcing known capacity, hard timing (planned_start <= now), and energy constraints.
    5. Invoke rank_candidates (#023) with Jev scores from JevService (#021).
    6. Assemble NextApiResponse inside standard JSON response envelope.

2. src/cli/commands/next.rs — CLI Subcommand

  • Define clap arguments for life next:
    #[derive(Debug, clap::Args)]
    pub struct NextArgs {
        /// Available window duration (e.g. 90m, 2h, 45m)
        #[clap(long, default_value = "90m")]
        pub window: String,
    
        /// Current energy level (low, medium, high)
        #[clap(long, default_value = "medium")]
        pub energy: EnergyLevel,
    
        /// Optional life area filter (e.g. career, health)
        #[clap(long)]
        pub area: Option<String>,
    
        /// Output raw JSON envelope
        #[clap(long)]
        pub json: bool,
    }
    
  • Dispatches HTTP POST to /v1/next.

3. src/cli/formatters/next.rs — Human Terminal View

  • Format recommendations cleanly for interactive terminal use:
    RECOMMENDED NEXT ACTIONS (Available: 90m | Energy: medium)
    
    1. Draft fictional portfolio outline [itm_career_portfolio]
       Deadline: 2030-04-12
       Why:
         - [Fact] Matches unmet weekday career floor
         - [Fact] Estimated duration (45m) fits within 90m window
         - [Jev] alignment.v1: 3.4/4 | readiness.v1: 2.8/3
    
    2. Review team pull request [itm_code_review]
       Deadline: None
       Why:
         - [Fact] Unknown duration (not guaranteed to fit)
         - [Jev] alignment.v1: 2.5/4 | consequence.v1: 2.0/4
       Unknowns: estimated_minutes
    

4. Integration Tests (tests/next_action_e2e.rs)

  • Test Scenario W07 end-to-end:
    • Setup: Mock Radicale containing a 120m task, a career task with unknown duration, an overdue task, and a completed task.
    • Execute: life next --window 90m --energy medium --json.
    • Assertions:
      • 200 OK status.
      • Exactly eligible items returned (120m task and completed task excluded).
      • Returns at most 3 items in documented lexicographic order.
      • Factual and semantic reasons separated.
      • Unknown duration labeled in unknowns.
      • Verification of zero state writes to Radicale or database.

Acceptance Criteria

  • Running life next --window 90m --energy medium --json returns HTTP 200 OK with up to 3 structured candidates (Scenario W07).
  • Factual reasons, semantic reasons, and unknown attributes are returned in strictly separated arrays.
  • Unknown duration or energy is explicitly reported in unknowns and not claimed to fit with certainty.
  • Live schedule is queried to evaluate hard timing and busy capacity; incomplete calendar coverage emits incomplete_calendar warning and avoids confident capacity claims (Scenario W07, docs/technical/jev.md:41).
  • Semantic score values in recommendations adhere to the 0-indexed level weighted average representation defined in the Jev score criteria.
  • When no tasks match constraints, returns HTTP 200 with an empty candidates list and diagnostic warnings explaining why tasks were excluded.
  • When Jev is unavailable or disabled, returns deterministic recommendations with warning judgment_unavailable.
  • Invoking life next performs zero writes to Radicale or SQLite.
  • Human terminal output contains no guilt-inducing messaging, fake urgency, or scolding text.

Notes

  • Window bound is 1–1440 minutes (24h), stated in the error message and tested.
  • excluded[] explains an empty candidate list instead of returning a bare empty array; considered states truncation.
  • Zero-write property is structural (no write primitive is referenced in src/api/next.rs) plus asserted empirically.
  • No dependency added.

Blocked by

  • #023: Multi-Criteria Lexicographic Next-Task Ranking
## Summary Implement the user-facing CLI command `life next` and REST API endpoint `POST /v1/next`, providing structured, top-3 task recommendations with strictly separated factual constraints, semantic judgments, unknown attributes, and model provenance. Enforce the inviolable invariant that recommendations are strictly read-only and advisory, performing zero calendar writes. ## Background & Technical Rationale The `life next` command is the primary entry point for a user deciding what actionable task to focus on right now. It orchestrates the policy filter (#022), the multi-criteria lexicographic ranker (#023), and the Jev advisory scoring adapter (#021). The interaction model enforces foundational architectural and usability invariants: 1. **Strictly Read-Only Advisory Contract:** - Calling `life next` or `POST /v1/next` performs **ZERO writes** to Radicale or SQLite. - Selecting, reviewing, or viewing a recommended task is NOT task completion. Task completion requires an explicit, separate instruction (`life task complete <ID> --etag <ETAG> --operation-id <OP_ID>`). - Calendar state remains completely untouched during recommendation generation. 2. **Standard API Envelope & Provenance:** - Responses use the canonical Life JSON envelope: ```json { "schema_version": 1, "request_id": "req_...", "status": "ok", "data": { "candidates": [...] }, "warnings": [], "error": null } ``` - Returns at most **three** candidates. - Each candidate includes: - `item_id`: Authoritative task identifier (`itm_...`). - `title`: Task summary string. - `deadline`: Nullable tagged date or instant (`{"kind": "date", "value": "2030-04-12"}`). - `factual_reasons`: Array of objective constraints (e.g. `"Fits 90m window"`, `"Matches unmet weekday career floor"`). - `semantic_reasons`: Array of Jev score assessments (e.g. `"alignment.v1 score 8.4/10: Strongly aligned with career goals"`). - `unknowns`: Array of missing attributes (e.g. `["estimated_minutes"]`). - `judgment_provenance`: Evaluation metadata (`model: "jev-1.13.0"`, `evaluated_at: "<ISO8601>"`). 3. **Graceful Handling of Zero Eligible Tasks:** - If all open tasks are excluded by the policy filter (e.g., all tasks exceed the window, or all require high energy), the endpoint returns `status: "ok"` with `candidates: []`. - It attaches diagnostic warnings explaining why candidates were filtered out (e.g., `{"code": "no_eligible_tasks", "message": "All 8 active tasks exceed the 45m window duration"}`). 4. **Human & Machine Interfaces:** - **Terminal Human View (default):** Displays a clear, non-judgmental terminal card/table. Highlights factual reasons, deadline urgency, and unknown data warnings. Contains **no guilt-driven messaging**, behavioral lecturing, or fake urgency. - **Machine JSON View (`--json`):** Strict, structured JSON payload matching the API schema for consumption by AI agents or shell scripts. ## Exact Technical References & Specifications ### CLI & API Route Contract From `docs/technical/cli-api.md` (lines 43, 57, 71, 90): | Command | Method and route | Scope | |---|---|---| | `life next --window 90m --energy medium` | POST /v1/next | read | - **Command Payload & Results:** - *Required / optional input:* `window` as positive duration (e.g., `90m`, `2h`), `energy` (`low` | `medium` | `high`); optional `area`. - *Response data:* Up to three `candidates` with `item_id`, `factual_reasons`, `semantic_reasons`, `unknowns`, `judgment_provenance`. - *Flags:* CLI period/window/energy flags map to same-named query fields for GET and body fields for POST. - *Common data types:* Energy enum is `low`/`medium`/`high`. Temporal values follow tagged date/instant representations. - **Error & Warning Identifiers (lines 108–127):** - Warnings: `judgment_unavailable`, `unknown_estimate`, `incomplete_calendar`. ### Workflow & Policy Defaults Contract From `docs/technical/workflows.md` (lines 27, 44–50): | Workflow | Input and reads | Decision and approval | Writes and observable result | Failure path | |---|---|---|---|---| | Next task | Available window and energy; live eligible tasks, deadlines and recorded qualifying outcomes | Apply deterministic constraints before semantic ranking; policy order owned by Jev page | At most three advisory choices, factual versus semantic reasons, no calendar writes | Unknown duration/energy shown, not fabricated; Jev unavailable leaves explicit deterministic candidates, not claimed semantic ranking | > "Policy exceptions are explicit, scoped and recorded with reason if supplied. No guilt-driven messaging, inferred completion, silent deletion or automatic idea promotion. Life-area categories are not diagnoses or rankings of human worth." ### Verification Scenario W07 From `docs/technical/verification.md` (line 21): | ID | Requirement and specification | Input / action | Observable acceptance | |---|---|---|---| | W07 | [Next action](jev.md#next-task-policy) | 90m, medium energy, deadlines and unmet career floor | At most three eligible choices in documented order; factual/semantic reasons separated; unknown estimate not claimed to fit | ### Illustrative JSON Response Payload ```json { "schema_version": 1, "request_id": "req_demo_next_01", "status": "ok", "data": { "candidates": [ { "item_id": "itm_career_portfolio", "title": "Draft fictional portfolio outline", "deadline": { "kind": "date", "value": "2030-04-12" }, "factual_reasons": [ "Matches unmet weekday career floor", "Estimated duration (45m) fits within available 90m window", "Required energy (medium) matches available energy" ], "semantic_reasons": [ "alignment.v1 score 3.4/4: Highly aligned with active career outcomes", "readiness.v1 score 2.8/3: Clear next physical action identified" ], "unknowns": [], "judgment_provenance": { "model": "jev-1.13.0", "evaluated_at": "2030-04-08T06:30:00Z" } }, { "item_id": "itm_code_review", "title": "Review team pull request", "deadline": null, "factual_reasons": [ "Estimated duration unknown; not claimed to fit with certainty" ], "semantic_reasons": [ "alignment.v1 score 2.5/4: Ongoing operational duty", "consequence.v1 score 2.0/4: Moderate consequence of delay" ], "unknowns": [ "estimated_minutes" ], "judgment_provenance": { "model": "jev-1.13.0", "evaluated_at": "2030-04-08T06:30:00Z" } } ] }, "warnings": [], "error": null } ``` ## What to Build ### 1. `src/api/routes/next.rs` — Axum API Route - Request body struct: ```rust #[derive(Debug, Deserialize)] pub struct NextTaskApiRequest { pub window: String, // e.g. "90m", "2h" pub energy: EnergyLevel, // low, medium, high pub area: Option<String>, } ``` - Duration parser converting human strings (`"90m"` $\rightarrow 90$, `"2h"` $\rightarrow 120$) into minutes. Rejects negative or unparseable durations as `invalid_input`. - Handler pipeline: 1. Retrieve live schedule/agenda for the target time window to evaluate known busy time and schedule coverage. If calendar coverage is incomplete, attach warning `incomplete_calendar` and note in candidate unknowns that free capacity cannot be confirmed with certainty (`docs/technical/jev.md:41`). 2. Retrieve live open VTODOs from `CalendarClient`. 3. Query SQLite timeline for today's career floor attainment. 4. Invoke `filter_candidate_tasks` (#022), enforcing known capacity, hard timing (`planned_start <= now`), and energy constraints. 5. Invoke `rank_candidates` (#023) with Jev scores from `JevService` (#021). 6. Assemble `NextApiResponse` inside standard JSON response envelope. ### 2. `src/cli/commands/next.rs` — CLI Subcommand - Define clap arguments for `life next`: ```rust #[derive(Debug, clap::Args)] pub struct NextArgs { /// Available window duration (e.g. 90m, 2h, 45m) #[clap(long, default_value = "90m")] pub window: String, /// Current energy level (low, medium, high) #[clap(long, default_value = "medium")] pub energy: EnergyLevel, /// Optional life area filter (e.g. career, health) #[clap(long)] pub area: Option<String>, /// Output raw JSON envelope #[clap(long)] pub json: bool, } ``` - Dispatches HTTP POST to `/v1/next`. ### 3. `src/cli/formatters/next.rs` — Human Terminal View - Format recommendations cleanly for interactive terminal use: ```text RECOMMENDED NEXT ACTIONS (Available: 90m | Energy: medium) 1. Draft fictional portfolio outline [itm_career_portfolio] Deadline: 2030-04-12 Why: - [Fact] Matches unmet weekday career floor - [Fact] Estimated duration (45m) fits within 90m window - [Jev] alignment.v1: 3.4/4 | readiness.v1: 2.8/3 2. Review team pull request [itm_code_review] Deadline: None Why: - [Fact] Unknown duration (not guaranteed to fit) - [Jev] alignment.v1: 2.5/4 | consequence.v1: 2.0/4 Unknowns: estimated_minutes ``` ### 4. Integration Tests (`tests/next_action_e2e.rs`) - Test Scenario W07 end-to-end: - Setup: Mock Radicale containing a 120m task, a career task with unknown duration, an overdue task, and a completed task. - Execute: `life next --window 90m --energy medium --json`. - Assertions: - 200 OK status. - Exactly eligible items returned (120m task and completed task excluded). - Returns at most 3 items in documented lexicographic order. - Factual and semantic reasons separated. - Unknown duration labeled in `unknowns`. - Verification of zero state writes to Radicale or database. ## Acceptance Criteria - [x] Running `life next --window 90m --energy medium --json` returns HTTP 200 OK with up to 3 structured candidates (Scenario W07). - [x] Factual reasons, semantic reasons, and unknown attributes are returned in strictly separated arrays. - [x] Unknown duration or energy is explicitly reported in `unknowns` and not claimed to fit with certainty. - [x] Live schedule is queried to evaluate hard timing and busy capacity; incomplete calendar coverage emits `incomplete_calendar` warning and avoids confident capacity claims (Scenario W07, `docs/technical/jev.md:41`). - [x] Semantic score values in recommendations adhere to the 0-indexed level weighted average representation defined in the Jev score criteria. - [x] When no tasks match constraints, returns HTTP 200 with an empty candidates list and diagnostic warnings explaining why tasks were excluded. - [x] When Jev is unavailable or disabled, returns deterministic recommendations with warning `judgment_unavailable`. - [x] Invoking `life next` performs zero writes to Radicale or SQLite. - [x] Human terminal output contains no guilt-inducing messaging, fake urgency, or scolding text. ## Notes - Window bound is 1–1440 minutes (24h), stated in the error message and tested. - `excluded[]` explains an empty candidate list instead of returning a bare empty array; `considered` states truncation. - Zero-write property is structural (no write primitive is referenced in `src/api/next.rs`) plus asserted empirically. - No dependency added. ## Blocked by - #023: Multi-Criteria Lexicographic Next-Task Ranking
yuris changed title from Issue #024: Next Action CLI & Advisory Provenance to Next Action CLI & Advisory Provenance 2026-09-22 20:02:35 +00:00
yuris closed this issue 2026-09-23 00:53:29 +00:00
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
yuris/life-service#24
No description provided.