Information Feed & RSS
vita has two distinct "feed" systems that share a code namespace (scripts/feed/)
but serve different audiences:
- The inter-agent feed / RSS+JSON digest β a token-protected daily narrative
about the vita agent system itself, published at
vita-rss.ham.xyzfor other people building AI agents. It deliberately filters out all personal-health and work content. - Feed v1, the in-app social feed β a ranked, threaded, social-style feed inside the React/iOS UI. Pluggable producers emit candidates from internal signals; an orchestrator promotes and composes them into items; a multi-stage ranker orders the slate; and bio-zack's reactions feed a preference-learning loop that reshapes future output.
This page documents the mechanism of both. It never shows feed content β the in-app feed contains personal material by design, and the inter-agent feed is defined precisely by what it leaves out.
Note: The feed system co-exists with two sibling systems documented elsewhere: the Media Signal Refinery (external article/podcast ingestion) and the Living Briefing (real-time dashboard). They are separate pipelines.
Part 1 β The Inter-Agent Feed (RSS + JSON)
What it is
A daily narrative digest written for other agent builders. Each entry is a short markdown narrative describing what happened inside the vita agent system that day β architectural decisions, failure modes and recoveries, emergent behaviors, forge (idea-workshop) experiments, hardware integrations, and how the human-AI relationship is evolving. It is served as both JSON Feed 1.1 and RSS 2.0, behind a bearer token, on a dedicated domain.
Generation pipeline (daily, 7am PT)
The feed_narrative loop runs once a day and walks three stages:
collect_daily_events(date) # scripts/feed/collect.py
β gathers events from overnight /sleep summary, tagged stream
β captures (file-this / observation only), daily scratch notes,
β and forge ideas modified today
βΌ
privacy.filter_events(events) # scripts/feed/privacy.py (PASS 1, pre-LLM)
β drops any event whose source β BLOCKED_SOURCES
β {garmin, cgm, sleep, health, vault, medical}
βΌ
narrative.generate_narrative() # scripts/feed/narrative.py
β LLM synthesizes a 3-layer narrative (thread / moves / edge)
β with novelty detection vs the last 3 days; emits {"skip": true}
β for thin or already-covered days
βΌ
privacy.edit_narrative(narrative) # scripts/feed/privacy.py (PASS 2, post-LLM)
β adversarial LLM "privacy editor" rewrites anything that crosses
β privacy boundaries (no [redacted] tags β natural rewrite)
βΌ
data/feed/narratives/YYYY-MM-DD.json
The narrative model is structured output: {thread, narrative_md, discussion_items[],
topics[], novelty_score, structure_used[]}. Days with fewer than 2 events, or where
the LLM judges nothing new, are stored as a thin-day entry with "skipped": true and
are excluded from the served feed.
The two-pass privacy filter is the whole point. Pass 1 is a hard structural drop by source type (no regex content matching). Pass 2 is an LLM editor with an explicit "dinner-party test" prompt β it removes medical conditions/medications, named people in the private sphere, employer internals, financial details, and the substance of private conversations, while never touching the terms
bio-zack,silicon-zack,sleep gate, activity mentions, orslack(as a data-source label). The narrator prompt also instructs the model to skip workplace content entirely before it ever reaches the editor.Note: The narrator and privacy editor are LLM calls. The exact model is a deployment detail and may rotate β see Model Guidance rather than hard-coding a model id from this page.
Serving the feed
The router (api/src/routers/feed.py) loads recent non-skipped narratives newest-first
and renders them. A token from data/feed/config.json gates the per-day endpoints;
an unknown token returns 404 (not 403, so the URL space is opaque). The dedicated
host vita-rss.ham.xyz is whitelisted in the API's domain guard
(api/src/middleware/domain_guard.py) and gets clean root-level shortcut routes
registered directly on the app.
| Endpoint | Auth | Purpose |
|---|---|---|
GET /api/v1/feed/{token}/feed.json |
token | JSON Feed 1.1 β primary agent format |
GET /api/v1/feed/{token}/feed.xml |
token | RSS 2.0 mirror for standard readers |
GET /{token}/feed.json |
token | Root shortcut (clean URL on vita-rss.ham.xyz) |
GET /{token}/feed.xml |
token | Root shortcut RSS |
GET /api/v1/feed/narratives |
none (internal) | List recent narrative entries |
GET /api/v1/feed/feed-url |
none (internal) | Returns the subscription URLs + token |
The JSON Feed embeds a _vita extension object per item carrying thread,
discussion_items, novelty_score, word_count, and sources_used.
config.json holds {token, feed_title, feed_description, author, email, max_items}
(max_items defaults to 30). The token is a secret β never commit or print it.
Running the public tunnel
./run rss-tunnel start # ngrok tunnel β vita-rss.ham.xyz
./run rss-tunnel status
./run rss-tunnel log
Like every vita service, the feed tunnel is managed through ./run, never systemd.
Part 2 β Feed v1: the in-app social feed
A full social-style feed for the iOS/React app. The data model and pipeline live in
scripts/feed/; the HTTP surface is the M7 section of api/src/routers/feed.py;
state is a single SQLite database at data/feed/feed.db.
PII: the in-app feed surfaces personal, health, and relationship signals on purpose β that is its job for bio-zack. This page documents only the engine. Never reproduce feed item bodies, comments, or producer payloads.
Pipeline at a glance
PRODUCERS βββΊ candidates βββΊ ORCHESTRATOR βββΊ feed_items βββΊ RANKER βββΊ ordered slate
(every 15m) (feed.db) (every 15m+5) (feed.db) (every 15m+7)
β β
bio-zack reacts ββββββββββββββββ΄ββββ iOS / React UI βββββββββββ
β like / bookmark / comment / reply / dismiss
βΌ
CLASSIFIER βββΊ routes βββΊ preferences Β· research tasks Β· proposals (M6) Β· dialogue Β· dismiss
β
ββββΊ PREFERENCE LEARNING + CONSTITUTIONS reshape future producer/composer output
The data model (scripts/feed/models.py)
Pydantic v2 models, each with from_row() / to_db_dict() for the SQLite layer.
The core lifecycle is candidate β item β interaction.
| Model | Role |
|---|---|
FeedCandidate |
Raw producer output, pending promotion. Carries item_kind, stakes, base_decay_lambda (default 0.02), deadline_at, reversible, composite_inputs, status. |
FeedItem |
Promoted, user-visible item. Adds voice_agent_id, rank_score, last_rationale, is_daily_anchor, is_red_tier, is_friction_required, and threading (parent_thread_id, thread_position, thread_total). |
FeedInteraction |
A bio-zack reaction: view, like, bookmark, comment, dismiss, expand, tap_to_chat. Comments/dismisses carry the classifier verdict. |
Preference |
A learned ranking/style rule (M8). |
Constitution / ConstitutionAmendment |
Per-agent governance docs + proposed amendments. |
WorkingContext |
A daily snapshot of "what bio-zack cares about now", fed to composer + ranker. |
EpisodicEvent / ArchivalChunk |
Append-only memory log of feed system events (ranking runs, classifications, amendments). |
ItemIntent / ItemOutcome |
For action-type items: what was intended, and whether it happened. |
ShadowCandidate / ComposerSkip / ComposerError |
M11a A/B shadow path + composer telemetry. |
Enums that matter for reimplementation:
| Enum | Values |
|---|---|
ItemKind |
awareness, research, action, reflection, composite, debate, proposal, system_update |
Stakes |
low, med, high |
CandidateStatus |
pending, promoted, merged, dropped, composing, composed, skipped_by_composer, composer_failed |
ClassifierType |
preference_rule, research_task, feature_request, dialogue, dismiss, observation, command, reflection, data |
RuleType |
downweight, upweight, topic_filter, producer_pref, style |
PreferenceStatus |
pending_review, active, rejected, reverted |
ConstitutionKind |
epistemic_shared, stylistic_per_agent |
AmendmentStatus |
pending, approved, rejected, reverted |
Producers (scripts/feed/producers/)
Producers implement a tiny Producer protocol (producer_id, voice_agent_id,
produce_candidates() -> list[FeedCandidate]). The runner iterates the
PRODUCERS registry, calls each, and inserts candidates into feed.db. A producer
exception is isolated (returns -1 in the results dict) and never blocks the others.
Per-producer cursor state is persisted under data/feed/producer_state/ so each
producer only emits new material; scripts/feed/backfills/ seeds history.
producer_id |
Source | Voice |
|---|---|---|
coach |
Coaching prescriptions | coach |
silicon-zack:sleep_reflection |
Overnight sleep reflection | silicon-zack |
silicon-zack:sentinel_anomaly |
Sentinel anomalies | silicon-zack |
silicon-zack:heartbeat_topic |
Heartbeat discussion topics | silicon-zack |
silicon-zack:identity_observation |
Identity observations | silicon-zack |
silicon-zack:x_bookmark |
Saved X/Twitter bookmarks | silicon-zack |
silicon-zack:hn |
Hacker News items | silicon-zack |
make_candidate() (in producers/base.py) stamps each candidate's
metadata.voice_agent_id so the orchestrator can route it to the right composer voice
at promotion time.
Orchestrator (scripts/feed/orchestrator/core.py)
Runs every tick (feed_orchestrator, every 15 min, +5 offset). One tick:
- Pull up to 200
pendingcandidates. - Cluster them (
clustering.py). Singletons go down the promotion path; multi-member clusters become composites viasynthesis.py(capped per tick byFEED_SYNTHESIS_CAP, default 5; leftovers stay pending for the next tick). - RED-tier detection (
detect_red_tier): a candidate is RED-tier whenstakes=highAND itsdeadline_atis within 24h, or whenmetadata.severity == "critical". RED-tier items are flagged for urgent surfacing. - Promote each singleton through one of three modes, resolved per producer by
composer/migration.py: - legacy β promote with the candidate's own template body.
- composer β call the per-voice composer (Claude) to write the item body;
a composer
Skipis authoritative (the item is dropped, not legacy-fallback'd). Composer exceptions fall back to legacy. - shadow β emit the legacy item and side-write a
ShadowCandidateso the new composer output can be A/B compared against the legacy path without affecting the live feed. - Batched composing (default on,
batched_composer_enabledindata/feed/composer/config.json): singletons are grouped by voice and composed in chunks (max_batch_size_per_voice, default 15) viacomposer/batch.pyto cut LLM round-trips. - Daily anchor β once per day,
anchor.pyselects a "hero" card surfaced at the top of the feed. - Reap candidates stuck in
composingfor more than 5 minutes.
Composer voices and their prompts live in scripts/feed/composer/prompts/
(voice_silicon-zack.md, voice_coach.md, a shared system prompt, a critic prompt,
etc.). The daily working context (feed_working_context, 4am PT) regenerates the
snapshot that grounds both composer and ranker in what bio-zack currently cares about.
Ranker (scripts/feed/ranker/core.py)
rank_feed() runs every tick (feed_ranker, every 15 min, +7 offset) as a
multi-stage pipeline:
- Pre-filter (
prefilter.py) β a fast, LLM-free weighted score over all rankable items:
score = 0.4·recency_decay # exp(-λ·hours_since), λ = base_decay_lambda
+ 0.2Β·producer_prior # Beta-Bernoulli cold-start prior, default 0.5
+ 0.2Β·topic_match # keyword overlap with working-context topics
+ 0.1Β·deadline_multiplier # 1.0 evergreen β up to 3.0 as deadline nears
+ 0.1Β·stakes_boost # low 0.0 / med 0.05 / high 0.15
- LLM rerank (
llm_rerank.py) β listwise rerank of the top 20 pre-filtered items usingranker/prompts/listwise_rerank.mdand the built ranker context. On any failure it falls back to pre-filter ordering (mode = "prefilter_fallback"). - Slate diversity (
slate_diversity.py) β a sliding-window constraint: at most 2 items from the samevoice_agent_idper window of 5. - Persist β LLM-reranked items get a score offset by +100 to sit above pre-filter
scores; the rest keep their pre-filter score. The run is logged as an
EpisodicEvent(feed_rank) withitems_scored,top_5_ids,cost_usd,mode.
No engagement-surveillance signals. The pre-filter explicitly bans
dwell_ms,scroll_velocity,scroll_depth,session_length, etc. β a defense-in-depth check (_validate_no_banned_signals) raises if any leak into the feature dict. The ranker learns from explicit reactions and producer priors, not from passively measured attention.
Cold-start priors (ranker/cold_start.py)
Producer priors bootstrap from existing engagement data so a brand-new producer is not scored blind. Engagement is mined from heartbeat history and the proactive message queue, then turned into a Beta-Bernoulli posterior mean:
alpha = engaged + 1
beta = ignored + 1
prior = alpha / (alpha + beta) # 0.5 for an unknown producer
Priors are written atomically to data/feed/producer_priors.json and retrained weekly
by the feed_cold_start_retrain loop (Sunday 3am PT). The current keyspace is
{silicon-zack, coach, doctor}.
Interactions and the comment classifier
The UI posts reactions to the item endpoints. Likes and bookmarks are idempotent per
(item_id, session_id). Comments and replies are two different primitives:
- A comment is a
FeedInteraction(type="comment")attached to one item. It is the conversational surface: bio-zack comments, and silicon-zack answers in the same chronological discussion. - A reply creates a new
FeedItemparented to the thread root and bumpsthread_totalacross the item thread. It is content-level feed threading, not the comment conversation.
Posting a comment returns as soon as the interaction is stored. The API then schedules
classify_and_route() as a process-local asyncio task (or a daemon thread when there is
no running event loop):
POST /items/{id}/comment
β
ββ store bio-zack COMMENT interaction
ββ background classify_and_route(interaction_id)
β
ββ classify for learning/action routing
β (preference, research, feature request, dismiss)
ββ always run the full-agent reply path
β
ββ store silicon-zack COMMENT + episodic event
ββ enqueue Telegram desk notification + permalink
The classifier (scripts/feed/classifier/core.py) uses the Claude daemon with read-only
tools and prompts/classify_comment.md. Per-type confidence thresholds remap uncertain
results to the dialogue fallback; the verdict is saved on bio-zack's interaction and
routed through scripts/feed/classifier/routes/:
| Classified as | Route | Effect |
|---|---|---|
preference_rule |
routes/preference_rule.py |
Extract durable ranking/style rules (M8). |
research_task |
routes/research_task.py |
Spawn a research task. |
feature_request |
(M6 pipeline) | Auto-commission a proposal report (below). |
dialogue |
No learning side effect | The universal reply step below owns the conversation. |
dismiss |
routes/dismiss.py |
Increment metadata.dismiss_count. |
Classification and conversation are deliberately independent. Every bio-zack comment
gets a reply attempt regardless of its classification, including when classification
fails. routes/dialogue.py reconstructs the full discussion from the post body plus
chronological comment interactions, then invokes a fresh full agent through the Claude
daemon. The reply agent has the normal toolset and autonomy: a remark can receive a
short answer, while a real request can research, run commands, or make proportionate
changes. Its 600-second timeout is independent of the 60-second read-only classifier.
A successful answer is another COMMENT interaction with
metadata.is_agent_reply=true, voice_agent_id, and
in_response_to_interaction_id. It also records a classifier.dialogue_reply
episodic event. Agent-authored interactions are guarded from re-entry so a reply cannot
trigger an agent-to-agent loop. The per-item ceiling is 12 silicon-zack replies; after
that, the route records TAP_TO_CHAT with reason=dialogue_cap_reached instead of
calling another agent.
The web discussion renders bio-zack and silicon-zack comments chronologically and shows
silicon-zack is thinking⦠while the latest visible comment is bio-zack's. A successful
reply also enqueues a feed_agent_reply Telegram notification with a deduplicated link
to /feed/post/<item-id>. That notification is a convenience: admission rejection does
not roll back the reply already stored in feed.db.
Bio-zack can also author a top-level post (POST /api/v1/feed/posts), which is
classified by classify_post against prompts/classify_post.md.
Discussion permalink and build handoff
/feed/post/<item-id> is the dedicated web page for one post, its feed-item thread, and
its comment discussion. It auto-focuses the comment composer and exposes copy id for
build. The copied value is the bare item UUID, which any Vita agent can resolve:
./run feed-show <full-id-or-unique-prefix>
scripts/feed/show_item.py prints the post, feed-item children, and chronological
comment discussion as Markdown. Exact IDs win; a prefix must resolve to exactly one of
the ten newest matches or the command exits with the candidate list. This is the bridge
from a lightweight feed discussion to a dedicated build session: paste the ID and say
"let's work on this feed item."
Failure and recovery behavior
| Failure | What remains true | Recovery |
|---|---|---|
| Classifier daemon/JSON/routing failure | The bio-zack comment is durable; the universal reply is still attempted. | Inspect api.log; learning side effects can be replayed only with an explicit classifier invocation. |
| Full-agent failure, timeout, or empty result | The comment remains visible, but no agent reply or Telegram notification is stored. | Comment again or explicitly rerun classify_and_route() for the interaction; there is no durable retry queue. |
| Telegram enqueue is rejected or delivery later fails | The feed reply and episodic event remain durable. | Open the permalink directly; queue diagnostics live in the proactive system. |
| API restarts after storing the comment | The process-local background task can be lost. | Re-run classification for the interaction or add another comment. |
The dedicated page invalidates its item query immediately after a comment, but it does
not poll continuously for the asynchronous reply. Window focus, another query refresh,
or the Telegram permalink reveals the completed answer; until then the visible
thinking⦠state can linger.
Preference learning (M8)
When a comment is classified as preference_rule, handle_preference_rule() extracts
one or more Preference rows. Each rule has a rule_type
(downweight/upweight/topic_filter/producer_pref/style), a rule_target, and
a weight. A high-confidence verdict (>= 0.95) activates the preference immediately;
otherwise it lands in pending_review for bio-zack to approve. Preferences move
through a small state machine, enforced server-side
(_ALLOWED_PREFERENCE_TRANSITIONS):
pending_review β active | rejected
active β reverted
rejected β active
reverted β active
Active preferences flow back into ranking (via producer priors / topic filters) and into composer style.
Constitutions (scripts/feed/constitutions/)
Constitutions are governance documents that shape how each voice writes:
- Epistemic (shared) β
kind = epistemic_shared. Immutable in the DB; amendments are rejected withepistemic_immutable. To change it, edit the code/seed. - Stylistic (per-agent) β
kind = stylistic_per_agent, one per voice. These are amendable.
They are idempotently seeded from constitutions/seed/*.md
(epistemic_shared.md, stylistic_silicon-zack.md, stylistic_coach.md,
stylistic_doctor.md, stylistic_arbiter.md). The extractor.py proposes amendments
from interaction evidence; approving an amendment (approve_amendment) deactivates the
current row, inserts a new version with the proposed rule appended to the body, bumps
version, and logs a constitution.amended episodic event. Amendments carry the
evidence interaction IDs so the UI can show the source quotes that justified each
proposed change.
Proposal commission + commit watcher (M6)
This closes a request β proposal β shipped loop entirely inside the feed:
- Commission (
feed_proposal_commission, every 5 min): for each pendingfeature_request,proposals/core.pymarks itgenerating, calls an LLM (proposals/generator.py+prompts/proposal_generator.md) to write a proposal report intodata/reports/, inserts aPROPOSALfeed item threaded to the original request, and marks itgenerated. Terminal states (generating/generated/shipped/dismissed/failed) prevent re-processing. - Commit watcher (
feed_commit_watcher, every 5 min):commit_watcher.pyscans git for commits whose message matchesfeed-proposal: <slug>, finds the matchingPROPOSALitem bymetadata.proposal_slug, and emits aSYSTEM_UPDATEitem threaded back to the original feature request β so the requester sees the shipped notice in the same thread. State (last scanned HEAD sha) lives atdata/feed/proposals/commit_watcher_state.json; cold start scans ~14 days back.
So to "ship" a feed-requested feature, the committer simply tags the commit:
git commit -m "feed: add dark-mode anchor card
feed-proposal: dark-mode-anchor"
Daily digest + derived signals (M11)
- Daily digest (
composer/digest.py,feed_daily_digest, 5pm PT) summarizes the day's feed activity for bio-zack. - Derived signals (
derived_signals.py, M11l) surface bio-zack's recent feed reactions, active preferences, pending proposals, the latest digest, and per-voice composer pulse counts into the Executive Loop's orient phase. This module lives outsidescripts/feed/composer/on purpose: an AST guard test (tests/test_feed_composer_no_engagement_in_prompt.py) forbids the composer prompt path from importing engagement symbols, so keeping derived signals out of that namespace makes any accidental coupling fail the test immediately.
Push notifications (M10)
The iOS app registers an APNs device token; the dispatcher
(scripts/feed/push/, apns.py + dispatcher.py) sends pushes (e.g. for RED-tier
items via red_tier.py) and tracks per-device failure_count.
| Endpoint | Purpose |
|---|---|
POST /api/v1/feed/push/register |
Register/refresh an APNs token (idempotent on token) |
POST /api/v1/feed/push/unregister |
Disable a token (preserves dispatch-log FK) |
GET /api/v1/feed/push/devices |
Debug: list active push devices |
In-app HTTP surface (M7)
| Endpoint | Purpose |
|---|---|
GET /api/v1/feed/items |
List ranked items, anchor-first, cursor paginated |
GET /api/v1/feed/items/{id} |
Item + thread + interactions |
GET /api/v1/feed/items/{id}/thread |
Just the thread children |
GET /api/v1/feed/anchor |
Today's daily anchor (falls back to most recent) |
POST /api/v1/feed/items/{id}/like Β· /bookmark Β· /comment Β· /reply Β· /dismiss |
Reactions |
POST /api/v1/feed/posts |
Bio-zack authored post |
GET /api/v1/feed/preferences Β· PATCH /api/v1/feed/preferences/{id} Β· DELETE β¦ |
Manage learned preferences |
GET /api/v1/feed/constitutions/active |
List active constitutions |
GET /api/v1/feed/constitutions/amendments?status=β¦ |
List amendments by status |
POST /api/v1/feed/constitutions/amendments/{id}/approve Β· /reject |
Resolve amendments |
GET /api/v1/feed/shadow Β· GET /api/v1/feed/shadow/stats |
M11a shadow A/B viewer (admin) |
All in-app endpoints run on the main API (./run api, port 33800); the public
read-only mirror is on port 33802; the Vite dev UI on 33801.
The corresponding discussion page is a web route, GET /feed/post/{item_id}, served
by the Vita UI rather than the /api/v1/feed router.
Scheduled loops
All feed loops are registered in api/src/loop_engine/registry.py and executed via
api/src/services/scheduler_feed.py. They are in-process scheduler loops, not cron.
| Loop | Cadence | What it does |
|---|---|---|
feed_producers |
Every 15 min | Run all producers, insert candidates |
feed_orchestrator |
Every 15 min (+5 offset) | Cluster, promote, compose, anchor |
feed_ranker |
Every 15 min (+7 offset) | Pre-filter β LLM rerank β slate diversity |
feed_working_context |
4am PT daily | Regenerate the working-context snapshot |
feed_narrative |
7am PT daily | Generate + privacy-filter the inter-agent digest |
feed_daily_digest |
5pm PT daily | Summarize the day's feed activity |
feed_cold_start_retrain |
Sunday 3am PT | Recompute producer priors |
feed_proposal_commission |
Every 5 min | Commission proposals for feature requests |
feed_commit_watcher |
Every 5 min | Emit system-update items for shipped proposals |
File Locations
| Path | Contents |
|---|---|
api/src/routers/feed.py |
RSS/JSON endpoints + M7 in-app API |
api/src/schemas/feed_v1.py |
Pydantic request/response schemas for M7 |
api/src/loop_engine/registry.py |
Loop registrations + cadences |
api/src/services/scheduler_feed.py |
Loop execution helpers |
scripts/feed/models.py |
Pydantic models + enums (candidate/item/interaction/β¦) |
scripts/feed/db.py |
SQLite data-access layer |
scripts/feed/collect.py Β· narrative.py Β· privacy.py |
Inter-agent digest pipeline |
scripts/feed/producers/ |
Producer registry + per-producer modules |
scripts/feed/orchestrator/ |
Promotion, clustering, synthesis, anchor |
scripts/feed/composer/ |
Per-voice LLM composers, batch, digest, drift audit |
scripts/feed/ranker/ |
Pre-filter, LLM rerank, cold-start, slate diversity |
scripts/feed/classifier/core.py |
Read-only classification, learning/action routing, universal reply orchestration |
scripts/feed/classifier/routes/dialogue.py |
Full-agent reply, 12-exchange cap, episodic event, Telegram permalink notification |
scripts/feed/show_item.py |
./run feed-show build-handoff resolver and Markdown renderer |
scripts/feed/constitutions/ |
Constitution bootstrap, extractor, seeds |
scripts/feed/proposals/ |
M6 commission + commit watcher |
scripts/feed/push/ |
APNs registration + dispatch |
scripts/feed/derived_signals.py |
M11l signals for the executive loop |
data/feed/feed.db |
All in-app feed state (SQLite) |
data/feed/config.json |
Inter-agent feed token + metadata (secret) |
data/feed/narratives/YYYY-MM-DD.json |
Daily narrative entries |
data/feed/producer_state/ |
Per-producer cursor state |
data/feed/producer_priors.json |
Cold-start ranking priors |
data/feed/composer/config.json |
Batched-composer config |
data/feed/proposals/commit_watcher_state.json |
Last-scanned git sha |
web/src/components/feed/CommentThread.tsx |
Chronological bio-zack/silicon-zack discussion UI |
web/src/components/feed/CopyItemId.tsx |
Bare-ID clipboard handoff |
web/src/routes/feed/post/$itemId.tsx |
Dedicated post permalink page |
Known limitations
- Comment classification and agent replies use process-local background work, not a durable job queue. A restart in the narrow post-insert/pre-reply window requires a manual replay or another comment.
- Each reply is a fresh daemon invocation whose prompt reconstructs the discussion; there is no long-lived agent session behind a feed item.
- The web discussion does not continuously poll for replies, and its comment-only
renderer does not currently surface the
TAP_TO_CHATcap interaction. - The 12-reply ceiling is counted per item, not across child
FeedItemthread members.
Where to go next
- Media Signal Refinery β external article/podcast ingestion (a separate pipeline)
- Living Briefing β real-time dashboard
- Executive Loop β consumes feed-derived signals in its orient phase
- Sentinel β anomaly source for the
sentinel_anomalyproducer - Model Guidance β model routing for the composer/ranker/classifier LLMs
- Proactive Outreach β admission and delivery of
feed_agent_replynotifications