Daily triage stuff

This commit is contained in:
2026-06-05 13:11:41 +02:00
parent 5ceb771a8f
commit 9a03677475
8 changed files with 356 additions and 39 deletions

View File

@@ -43,22 +43,22 @@ editing canonical workplans.
## Runner Status ## Runner Status
This definition is intentionally `enabled: false`. This definition is `enabled: true` and is the active owned runner for the daily
WSJF triage loop.
Current active runner: Current active runner:
- Codex app automation: `daily-state-hub-wsjf-triage` - activity-core Temporal schedule from this ActivityDefinition
- Prompt source: - Prompt source:
`/home/worsch/the-custodian/runtime/prompts/daily_statehub_wsgi_triage.md` `/home/worsch/the-custodian/runtime/prompts/daily_statehub_wsgi_triage.md`
Durable target runner: Deprecated fallback runner:
- activity-core Temporal schedule using this ActivityDefinition - Codex app automation: `daily-state-hub-wsjf-triage`
Enable this definition only after activity-core has a reviewed action path for Do not run both substrates at the same time. If activity-core is disabled for a
running a Custodian agent/report instruction and writing the resulting working recovery drill or incident, record the operator decision before re-enabling any
memory note/progress event. Until then, activity-core is the target substrate Codex app fallback.
and the Codex automation is the active runner. Do not run both at the same time.
## Trigger ## Trigger
@@ -92,7 +92,7 @@ trusted_fields:
- context.daily_triage_digest - context.daily_triage_digest
model: custodian-triage-balanced model: custodian-triage-balanced
temperature: 0.2 temperature: 0.2
max_tokens: 1400 max_tokens: 1800
max_depth: 2 max_depth: 2
model_params: model_params:
reasoning_effort: medium reasoning_effort: medium
@@ -105,6 +105,12 @@ prompt: |
canon, workplans, deployments, secrets, money/legal commitments, or external canon, workplans, deployments, secrets, money/legal commitments, or external
publication. publication.
Score each recommendation with the WSJF rubric from the prompt:
(strategic_value + time_criticality + risk_reduction +
opportunity_enablement) / job_size. Use integer factor values from 1 to 5,
round score to one decimal place, sort recommendations by rank, and return at
most 10 recommendations.
Curated digest: Curated digest:
{context.daily_triage_digest} {context.daily_triage_digest}
@@ -115,10 +121,19 @@ prompt: |
"summary": "short operator-facing summary", "summary": "short operator-facing summary",
"recommendations": [ "recommendations": [
{ {
"rank": 1,
"candidate": "workplan or task id/slug", "candidate": "workplan or task id/slug",
"action": "work-next|revisit|split|park|close-out|needs-human|needs-cross-agent|needs-consistency-sync", "action": "work-next|revisit|split|park|close-out|needs-human|needs-cross-agent|needs-consistency-sync",
"why": "brief reason", "why": "brief reason",
"confidence": "high|medium|low" "confidence": "high|medium|low",
"wsjf": {
"score": 8.5,
"strategic_value": 5,
"time_criticality": 4,
"risk_reduction": 4,
"opportunity_enablement": 4,
"job_size": 2
}
} }
] ]
} }

View File

@@ -0,0 +1,60 @@
# Daily State Hub WSJF Calibration - 2026-06-04
## Source Runs
| Date | Run id | Source | Main work-next recommendations |
|------|--------|--------|--------------------------------|
| 2026-06-02 | `f9b97749` | activity-core manual canary | `cust-wp-0044`, `cust-wp-0045` |
| 2026-06-03 | `6d2737e3` | activity-core daily run | `cust-wp-0044`, `cust-wp-0045`, WHI card |
| 2026-06-04 | `65e273bf` | activity-core daily run | `cust-wp-0044`, `cust-wp-0045`, `cust-wp-0003` |
The three runs were consecutive activity-core-generated Custodian daily triage
notes under `memory/working/`. They are sufficient to calibrate
`CUST-WP-0044-T06` and `CUST-WP-0045-T08` because none of the three came from
the old Codex app automation fallback.
## Findings
- The top recommendations were stable and actionable. The system repeatedly
identified the triage loop itself (`CUST-WP-0044`) and the activity-core
runner cutover (`CUST-WP-0045`) as the highest-value next work.
- The recommendations matched actual follow-up work: `CUST-WP-0045-T07` was
closed through the State Hub WSJF review surface, and this calibration closes
the remaining `CUST-WP-0044-T06` / `CUST-WP-0045-T08` loop.
- The action vocabulary is useful. `work-next`, `needs-human`, `revisit`,
`needs-consistency-sync`, and `close-out` all appeared in sensible places.
- The maximum recommendation count is about right. Seven to nine items were
enough to surface the important work without turning the note into a backlog.
- Stale or blocked work should remain explicit recommendations, not automatic
status changes. The daily run should continue to recommend `revisit`,
`park`, or `needs-human` and leave canonical edits to implementation
sessions.
- The current JSON reports were too thin for the stated WSJF contract. They
explained why candidates were selected but did not include component scores.
The schema and ActivityDefinition prompt now require rank and WSJF component
scores for future runs.
- The ActivityDefinition body still described the old disabled/fallback
posture even though frontmatter had `enabled: true`. The runner-status text
was corrected during calibration.
## Calibration Decisions
- Keep equal WSJF factor weights for now.
- Keep the recommendation cap at 10.
- Keep `max_depth: 2` and the balanced triage profile for ordinary daily runs.
- Increase the runner `max_tokens` from 1400 to 1800 to make room for WSJF
component scores without losing recommendations.
- Require explicit WSJF fields in the executable JSON schema:
`score`, `strategic_value`, `time_criticality`, `risk_reduction`,
`opportunity_enablement`, and `job_size`.
- Treat stale-but-intentionally-parked work as a recommendation quality issue,
not a status automation issue.
- Use the State Hub WSJF review page plus activity-core metadata as the normal
"did it run today?" surface.
## Result
The daily activity-core WSJF triage loop is useful enough to continue as a
standing Custodian habit. The next executable recommendation after this
closeout is `CUST-WP-0003` / WHI KPI card work, unless a human-gated item is
explicitly approved first.

View File

@@ -0,0 +1,75 @@
---
type: working-memory
source: activity-core
activity_id: 6fca51fa-387a-4fd0-bc4e-d62c29eb859a
activity_core_run_id: 65e273bf-08f1-5b3c-ace7-191ca32bf468
instruction_id: daily-triage-report
scheduled_for: None
created: 2026-06-04T05:21:12.224105+00:00
---
# Daily State Hub WSJF Triage - 2026-06-04
11 active workstreams with 3 high-priority items needing immediate attention. CUST-WP-0044 (this triage system) and CUST-WP-0045 (daily runner) are in calibration phase. HF-WP-0001 has 5 human-needed tasks blocking ops-hub extension. One workstream blocked, infrastructure migration work distributed across multiple streams.
```json
{
"recommendations": [
{
"action": "work-next",
"candidate": "cust-wp-0044",
"confidence": "high",
"why": "High priority, active calibration of this triage system itself"
},
{
"action": "work-next",
"candidate": "cust-wp-0045",
"confidence": "high",
"why": "High priority daily runner cutover, supports triage automation"
},
{
"action": "needs-human",
"candidate": "hf-wp-0001",
"confidence": "high",
"why": "High priority but 5 tasks need human input, blocking ops-hub extension"
},
{
"action": "revisit",
"candidate": "cust-wp-0046",
"confidence": "medium",
"why": "Blocked status with 1 human-needed task, assess unblocking conditions"
},
{
"action": "needs-human",
"candidate": "rail-ho-wp-0005",
"confidence": "medium",
"why": "Large workstream (11 tasks) with 4 human-needed items including high-priority design decisions"
},
{
"action": "work-next",
"candidate": "cust-wp-0003",
"confidence": "medium",
"why": "9 todo tasks, all high priority, no human intervention needed"
},
{
"action": "needs-consistency-sync",
"candidate": "cust-wp-0011",
"confidence": "medium",
"why": "Infrastructure migration overlaps with CUST-WP-0038, coordinate sequencing"
},
{
"action": "close-out",
"candidate": "state-wp-0052",
"confidence": "high",
"why": "No open tasks remaining, appears complete"
},
{
"action": "close-out",
"candidate": "ihub-wp-0018",
"confidence": "high",
"why": "No open tasks remaining, appears complete"
}
],
"summary": "11 active workstreams with 3 high-priority items needing immediate attention. CUST-WP-0044 (this triage system) and CUST-WP-0045 (daily runner) are in calibration phase. HF-WP-0001 has 5 human-needed tasks blocking ops-hub extension. One workstream blocked, infrastructure migration work distributed across multiple streams."
}
```

View File

@@ -4,7 +4,7 @@ type: runtime-prompt
owner: custodian owner: custodian
status: active status: active
created: "2026-05-17" created: "2026-05-17"
updated: "2026-05-17" updated: "2026-06-04"
related_workplan: CUST-WP-0044 related_workplan: CUST-WP-0044
--- ---
@@ -17,12 +17,12 @@ reviewable recommendation report. The review is a focus surface, not an
execution loop: it recommends what to work next, what to revisit, what to park, execution loop: it recommends what to work next, what to revisit, what to park,
and what needs human or cross-agent attention. and what needs human or cross-agent attention.
Do not create a new scheduler for this loop. The current runner is the Codex Do not create a new scheduler for this loop. The current runner is activity-core
app automation `daily-state-hub-wsjf-triage`. The durable target substrate is using the ActivityDefinition in
activity-core using the ActivityDefinition in
`/home/worsch/the-custodian/activity-definitions/daily-statehub-wsjf-triage.md`. `/home/worsch/the-custodian/activity-definitions/daily-statehub-wsjf-triage.md`.
If the runner is moved to activity-core later, disable the Codex app automation Do not re-enable a Codex app fallback unless activity-core is deliberately
first so there is exactly one daily runner. disabled and that operator decision is recorded, so there is exactly one daily
runner.
## Operating Rules ## Operating Rules
@@ -35,6 +35,8 @@ first so there is exactly one daily runner.
wording, stale frontmatter, and missing file-backed state. wording, stale frontmatter, and missing file-backed state.
- Treat scores as prioritization aids, not truth. Every WSJF row needs a - Treat scores as prioritization aids, not truth. Every WSJF row needs a
confidence label. confidence label.
- Include explicit WSJF component scores in executable JSON output so the
working-memory note remains auditable without re-running the model.
- Items involving money, legal status, secrets, security posture, external - Items involving money, legal status, secrets, security posture, external
publication, or external reputation must be reported as `needs-human` unless publication, or external reputation must be reported as `needs-human` unless
a current explicit approval already exists. a current explicit approval already exists.
@@ -235,3 +237,21 @@ When the State Hub API is reachable, append one progress event for the review:
Do not mark tasks done from the daily run itself. Task status changes belong to Do not mark tasks done from the daily run itself. Task status changes belong to
the implementation session that applies a recommendation. the implementation session that applies a recommendation.
## Executable JSON Shape
When the activity-core runner asks for JSON only, return the same content in
the schema at `/home/worsch/the-custodian/schemas/daily-triage-report.json`.
Each recommendation must include:
- `rank`
- `candidate`
- `action`
- `why`
- `confidence`
- `wsjf.score`
- `wsjf.strategic_value`
- `wsjf.time_criticality`
- `wsjf.risk_reduction`
- `wsjf.opportunity_enablement`
- `wsjf.job_size`

View File

@@ -1,27 +1,89 @@
{ {
"type": "object", "type": "object",
"required": ["summary", "recommendations"], "required": ["summary", "recommendations"],
"additionalProperties": false,
"properties": { "properties": {
"summary": { "summary": {
"type": "string" "type": "string"
}, },
"recommendations": { "recommendations": {
"type": "array", "type": "array",
"minItems": 1,
"maxItems": 10,
"items": { "items": {
"type": "object", "type": "object",
"required": ["candidate", "action", "why", "confidence"], "required": ["rank", "candidate", "action", "why", "confidence", "wsjf"],
"additionalProperties": false,
"properties": { "properties": {
"rank": {
"type": "integer",
"minimum": 1,
"maximum": 10
},
"candidate": { "candidate": {
"type": "string" "type": "string"
}, },
"action": { "action": {
"type": "string" "type": "string",
"enum": [
"work-next",
"revisit",
"split",
"park",
"close-out",
"needs-human",
"needs-cross-agent",
"needs-consistency-sync"
]
}, },
"why": { "why": {
"type": "string" "type": "string"
}, },
"confidence": { "confidence": {
"type": "string" "type": "string",
"enum": ["high", "medium", "low"]
},
"wsjf": {
"type": "object",
"required": [
"score",
"strategic_value",
"time_criticality",
"risk_reduction",
"opportunity_enablement",
"job_size"
],
"additionalProperties": false,
"properties": {
"score": {
"type": "number"
},
"strategic_value": {
"type": "integer",
"minimum": 1,
"maximum": 5
},
"time_criticality": {
"type": "integer",
"minimum": 1,
"maximum": 5
},
"risk_reduction": {
"type": "integer",
"minimum": 1,
"maximum": 5
},
"opportunity_enablement": {
"type": "integer",
"minimum": 1,
"maximum": 5
},
"job_size": {
"type": "integer",
"minimum": 1,
"maximum": 5
}
}
} }
} }
} }

View File

@@ -4,13 +4,13 @@ type: workplan
title: "Daily State Hub WSJF Triage" title: "Daily State Hub WSJF Triage"
domain: custodian domain: custodian
repo: the-custodian repo: the-custodian
status: active status: finished
owner: custodian owner: custodian
topic_slug: custodian topic_slug: custodian
planning_priority: high planning_priority: high
planning_order: 44 planning_order: 44
created: "2026-05-17" created: "2026-05-17"
updated: "2026-05-17" updated: "2026-06-04"
state_hub_workstream_id: "99993845-be6a-401d-be98-f8107014abed" state_hub_workstream_id: "99993845-be6a-401d-be98-f8107014abed"
--- ---
@@ -249,7 +249,7 @@ governance boundaries.
```task ```task
id: CUST-WP-0044-T06 id: CUST-WP-0044-T06
status: todo status: done
priority: medium priority: medium
depends_on: [CUST-WP-0044-T02, CUST-WP-0044-T03, CUST-WP-0044-T04, CUST-WP-0044-T05] depends_on: [CUST-WP-0044-T02, CUST-WP-0044-T03, CUST-WP-0044-T04, CUST-WP-0044-T05]
state_hub_task_id: "46c48fde-bb5f-4fe5-98cc-9cf788c98fc5" state_hub_task_id: "46c48fde-bb5f-4fe5-98cc-9cf788c98fc5"
@@ -269,6 +269,23 @@ Calibrate:
Done when the daily review is useful enough to continue as a standing Done when the daily review is useful enough to continue as a standing
Custodian habit. Custodian habit.
**2026-06-04:** T06 complete after reviewing three consecutive activity-core
daily triage notes:
- `memory/working/daily-triage-2026-06-02-f9b97749.md`
- `memory/working/daily-triage-2026-06-03-6d2737e3.md`
- `memory/working/daily-triage-2026-06-04-65e273bf.md`
The recommendations were stable and useful: `CUST-WP-0044` and `CUST-WP-0045`
were repeatedly ranked as high-confidence `work-next`, human-gated work stayed
clearly marked, and `CUST-WP-0046` remained visible as a revisit/unblock item.
Calibration notes live in
`docs/daily-statehub-wsjf-calibration-2026-06-04.md`. The main adjustment is
that future executable JSON reports must include rank plus WSJF component
scores; the schema, prompt, and ActivityDefinition were updated accordingly.
The daily review is useful enough to continue as a standing Custodian habit,
so `CUST-WP-0044` is finished.
## Acceptance Criteria ## Acceptance Criteria
- The Custodian has a daily review path for the State Hub situation. - The Custodian has a daily review path for the State Hub situation.
@@ -282,7 +299,7 @@ Custodian habit.
## Implementation Notes - 2026-05-17 ## Implementation Notes - 2026-05-17
The daily triage loop now has two explicit layers: The daily triage loop initially had two explicit layers:
- Active runner: Codex app automation `daily-state-hub-wsjf-triage`. - Active runner: Codex app automation `daily-state-hub-wsjf-triage`.
- Durable target substrate: activity-core ActivityDefinition draft at - Durable target substrate: activity-core ActivityDefinition draft at
@@ -292,12 +309,13 @@ The reusable prompt, report template, WSJF procedure, loose-end detectors, and
recommendation gates live in recommendation gates live in
`runtime/prompts/daily_statehub_wsgi_triage.md`. `runtime/prompts/daily_statehub_wsgi_triage.md`.
The activity-core definition is intentionally disabled while the Codex As of 2026-05-17, the activity-core definition was intentionally disabled while
automation is the active runner. It exists so the schedule and context contract the Codex automation was the active runner. It existed so the schedule and
are reviewable in the same Markdown-as-definition style activity-core already context contract were reviewable in the same Markdown-as-definition style
uses. When activity-core can run a Custodian agent/report action directly, this activity-core already uses. The intended target was to enable that definition
definition should be enabled and the Codex app automation disabled, keeping a and disable the Codex app automation once activity-core could run the
single daily runner. Custodian report action directly.
T06 remains open because it requires three actual daily runs and calibration This changed after the activity-core cutover in `CUST-WP-0045`; the
against follow-up work. ActivityDefinition is now the active runner and the three-run calibration is
complete.

View File

@@ -4,13 +4,13 @@ type: workplan
title: "Activity-Core Daily Triage Runner Cutover" title: "Activity-Core Daily Triage Runner Cutover"
domain: custodian domain: custodian
repo: the-custodian repo: the-custodian
status: active status: finished
owner: custodian owner: custodian
topic_slug: custodian topic_slug: custodian
planning_priority: high planning_priority: high
planning_order: 45 planning_order: 45
created: "2026-05-19" created: "2026-05-19"
updated: "2026-06-02" updated: "2026-06-04"
state_hub_workstream_id: "d9d9a3ec-f736-4041-beac-bb92c7ad314e" state_hub_workstream_id: "d9d9a3ec-f736-4041-beac-bb92c7ad314e"
--- ---
@@ -264,7 +264,7 @@ has completed successfully.
```task ```task
id: CUST-WP-0045-T07 id: CUST-WP-0045-T07
status: todo status: done
priority: medium priority: medium
depends_on: [CUST-WP-0045-T06] depends_on: [CUST-WP-0045-T06]
state_hub_task_id: "b977c721-cadc-461f-8ffb-715d438e4c31" state_hub_task_id: "b977c721-cadc-461f-8ffb-715d438e4c31"
@@ -288,7 +288,7 @@ without inspecting Codex Desktop session internals.
```task ```task
id: CUST-WP-0045-T08 id: CUST-WP-0045-T08
status: todo status: done
priority: medium priority: medium
depends_on: [CUST-WP-0045-T06, CUST-WP-0045-T07] depends_on: [CUST-WP-0045-T06, CUST-WP-0045-T07]
state_hub_task_id: "f4a985fd-8cce-4175-983e-cf3b437e19a5" state_hub_task_id: "f4a985fd-8cce-4175-983e-cf3b437e19a5"
@@ -650,6 +650,51 @@ activity-core schedule stays paused.
surfaces, identical run_id across all four (file, progress event, surfaces, identical run_id across all four (file, progress event,
ActivityRun row, Temporal result) ActivityRun row, Temporal result)
## Implementation Notes - 2026-06-04
T07 is done by reusing the State Hub WSJF Triage review page from
`STATE-WP-0053` instead of adding another Custodian-local status surface.
Evidence:
- `STATE-WP-0053` is `finished`; its dashboard page is reachable from
Workstreams -> WSJF Triage and implemented in the State Hub dashboard at
`dashboard/src/wsjf-triage.md`.
- The page reads `/progress/?event_type=daily_triage&limit=14` and
`/workstreams/workplan-index`, shows live/last-updated state, recent reports,
report detail, linked recommendations, a 14-day pattern view, and run
metadata.
- The detail metadata includes `scheduled_for`, `activity_core_run_id`,
`activity_id`, `instruction_id`, and the working-memory note path, which is
enough for the operator to answer "did it run today?" from owned
State Hub/activity-core telemetry without inspecting Codex Desktop session
internals.
- The deeper command-level diagnostics remain in
`workplans/CUST-WP-0045-cutover-runbook.md`, including Temporal schedule /
workflow and ActivityRun checks.
- The schedule and missed-run policy remain documented in
`activity-definitions/daily-statehub-wsjf-triage.md`: daily at 07:20
Europe/Berlin with `misfire_policy: skip`. The chosen behavior is no
catch-up; if the activity-core host is offline at 07:20, the absence is
visible as no new report/event for that date.
T08 is done by the same 2026-06-04 calibration pass that closes
`CUST-WP-0044-T06`. Evidence:
- `memory/working/daily-triage-2026-06-02-f9b97749.md`
- `memory/working/daily-triage-2026-06-03-6d2737e3.md`
- `memory/working/daily-triage-2026-06-04-65e273bf.md`
- `docs/daily-statehub-wsjf-calibration-2026-06-04.md`
The three reports were produced by activity-core and were compared against the
actual follow-up work. The loop correctly kept `CUST-WP-0044` and
`CUST-WP-0045` at the top until this closeout, kept human-gated work separate,
and surfaced `CUST-WP-0046` as a blocked/revisit candidate instead of asking
for unsafe fallback retirement. Calibration tightened the executable report
schema so future runs include explicit WSJF ranks and component scores. With
T06, T07, and T08 complete, the activity-core daily triage runner cutover is
finished.
## Acceptance Criteria ## Acceptance Criteria
- The daily State Hub WSJF triage runs from activity-core, not Codex app cron. - The daily State Hub WSJF triage runs from activity-core, not Codex app cron.

View File

@@ -4,13 +4,13 @@ type: workplan
title: "Activity-Core Hourly RecentlyOnScope Reports" title: "Activity-Core Hourly RecentlyOnScope Reports"
domain: custodian domain: custodian
repo: the-custodian repo: the-custodian
status: blocked status: finished
owner: codex owner: codex
topic_slug: custodian topic_slug: custodian
planning_priority: high planning_priority: high
planning_order: 46 planning_order: 46
created: "2026-05-22" created: "2026-05-22"
updated: "2026-05-23" updated: "2026-06-04"
state_hub_workstream_id: "671153ff-55bc-4ace-aa97-f322ca76ab3c" state_hub_workstream_id: "671153ff-55bc-4ace-aa97-f322ca76ab3c"
--- ---
@@ -245,11 +245,10 @@ Done when both manual and scheduled canaries leave complete evidence.
```task ```task
id: CUST-WP-0046-T06 id: CUST-WP-0046-T06
status: blocked status: done
priority: high priority: high
depends_on: [CUST-WP-0046-T05] depends_on: [CUST-WP-0046-T05]
state_hub_task_id: "2a46a6c8-4d3e-4064-a935-c90ca0c76a6d" state_hub_task_id: "2a46a6c8-4d3e-4064-a935-c90ca0c76a6d"
blocking_reason: "Manual and scheduled hourly RecentlyOnScope canaries passed, but daily-state-hub-wsjf-triage is the CUST-WP-0045 daily WSJF fallback. Do not pause or delete it under CUST-WP-0046 until the daily WSJF activity-core definition has its own canary and cutover decision."
``` ```
Remove the Codex app automation fallback from the operating path. Remove the Codex app automation fallback from the operating path.
@@ -380,6 +379,29 @@ Remaining gate:
Codex app automation here would remove daily WSJF coverage before Codex app automation here would remove daily WSJF coverage before
`CUST-WP-0045` has its own canary. `CUST-WP-0045` has its own canary.
This gate cleared on 2026-06-04 after `CUST-WP-0045` finished its activity-core
daily WSJF cutover and `CUST-WP-0044` finished the three-run calibration.
## Implementation Evidence - 2026-06-04
T06 is done.
- Decision: keep the old Codex Desktop automation
`daily-state-hub-wsjf-triage` paused rather than deleting it immediately.
This preserves a recoverable fallback without allowing it to run as a second
routine scheduler.
- Evidence: local Codex automation metadata shows
`daily-state-hub-wsjf-triage` with `status = "PAUSED"`.
- `activity-definitions/daily-statehub-wsjf-triage.md` is `enabled: true` and
now names activity-core as the active owned runner.
- `CUST-WP-0045` is finished; its T08 calibration used the June 2-4
activity-core daily triage notes.
- The 2026-06-04 calibration also updated the daily triage prompt/schema so
future reports include explicit WSJF ranks and component scores.
- Result: routine Custodian reporting no longer depends on an active Codex app
automation fallback. Daily WSJF and hourly RecentlyOnScope now both have
owned activity-core paths, so `CUST-WP-0046` is finished.
## Acceptance Criteria ## Acceptance Criteria
- Hourly RecentlyOnScope reports are generated by activity-core, not Codex app - Hourly RecentlyOnScope reports are generated by activity-core, not Codex app