Repository files navigation

activity-frames powering Nocta

arXivHackerNoonHugging FacePyPIDownloadsDownloads/monthGitHub starsPythonMCPtestsLicense: MITWebsite

Turn your workday into structured workflows agents can execute.

Computer-use agents work every task out from scratch, even one you've done a hundred times. And between tasks, your agent has no idea what you've been doing all day, so it starts every conversation blind.

activity-frames fixes both. It records your screen locally and compiles what it sees into structured activity frames: bounded, deterministic records of the tasks you actually did. The recurring ones become workflows an agent can execute instead of working out again. So your repetitive computer tasks get done cheaper (running a compiled workflow costs almost no tokens) and more reliable (the same steps, grounded the same way every time, instead of guessing from a screenshot) - and everything else becomes context your agent can use.

pip install activity-frames
aframes record # start capturing (local, audio off by default)
aframes context # your last 2 hours, agent-ready

Want to go deeper?docs/mcp.md covers the six MCP tools, docs/python.md the Python API, docs/cli.md every flag, docs/troubleshooting.md the errors you might actually hit. AI agent integrating this? AGENTS.md is written for you.

What your agent sees

Capture stores instants: thousands of snapshot rows a day, each one saying "at 22:53:05, Chrome showed linkedin.com/in/...". Useless to reason over.

activity-frames compiles those instants into activity frames:

- id: f-0007app: Google Chromesite: linkedin.comstart: "20:24:04"end: "20:42:11"duration_min: 18.0pages:
- {kind: people_search, entity: "cto berlin", count: 2}
- {kind: profile, entity: john-doe}
- {kind: company, entity: acme-ai}input: {keys: 214, clicks: 31}evidence: {frame_ids: "99871..100147"}

And into a compact context block for any system prompt:

USER ACTIVITY (2026-08-10, local time; measured from screen capture, no interpretation):
coverage: 10:29-22:55, 529 active min, 15 apps
away: 13:02-14:39 (97m)
- 10:54-11:23 Google Chrome/app.cal.com (28.7m): dashboard:video x59
- 12:49-12:52 cmux (2.7m): ⠂ Build nocta-recorder ground-up screen recorder
- 12:52-13:02 Google Chrome/calendar.google.com (9.7m): calendar x20
- 20:53-20:56 Google Chrome/github.com (3.3m): user:bethvourc x5; repo:nossa-y/activity-frames x2
- 20:56-21:01 Google Chrome/hackernoon.com (5.2m): page:i-compiled-55-days-of-screen-activity-into-episodic-memory-for-my-ai-agent x12

That is a real day of mine (excerpt; the full block has 44 frames). It compiles to 1,371 tokens; the raw snapshot rows for the same day are 247,563. Compiles in under a second, costs zero tokens, no LLM in the loop.

Drop it into a prompt and your agent knows your day - ask "what was that article I had open around 9pm?" and it answers in one line, off the block alone:

aframes demo: compile a real day, ask the agent, get a one-line answer

Real exchange, MCP disabled - the answer comes from the compiled block only.

Workflows agents can execute

Computer-use agents re-derive every task from scratch - screenshot, reason, act, repeat - even for a workflow they've run a hundred times. That re-derivation is where the token cost goes, and it's waste: the workflow hasn't changed.

Because activity-frames compiles recurring activity deterministically, a task you've demonstrated becomes an executable script:

aframes steps --find "message john doe"
{
"steps": [
{"t": "20:24:09", "op": "focus", "target": "Google Chrome · LinkedIn", "n": 1},
{"t": "20:24:14", "op": "click", "target": "Search", "role": "TextField", "url": "https://www.linkedin.com/feed/", "n": 2},
{"t": "20:24:16", "op": "type", "chars": 8, "text": "john doe", "n": 3},
{"t": "20:24:21", "op": "click", "target": "John Doe", "role": "Link", "url": "https://www.linkedin.com/search/results/people/", "n": 4},
{"t": "20:24:29", "op": "click", "target": "Message", "role": "Button", "url": "https://www.linkedin.com/in/john-doe/", "n": 5},
{"t": "20:24:35", "op": "type", "chars": 71, "text": "hey, loved your post on agent memory - open to a quick chat next week?", "n": 6}
],
"step_count": 6,
"unresolved_clicks": 0
}

That's the replay view of a demonstrated run - ordered clicks grounded by element name and role, typed runs, focus changes. An agent repeats the task instead of re-deriving it: fill the slots with new values (a different name, the same steps) and execute. On the happy path it replays at zero model calls; anything unexpected halts and asks instead of guessing.

We measured how much agents overpay to re-derive workflows they've already performed - the Routine Overhead Ratio - on weeks of real activity, replicated it on a public web-task dataset, and built a deterministic executor that replays a compiled workflow in a real browser. Instrument, measurements, and executor: research/.

Passively-captured activity becomes deterministic action - and the cheapest computer task is the one an agent never reasons through twice.

Measured, not guessed

Agent memory today means conversation memory: what you told the model. What you actually did is the missing half - and the hard part is representing it without lying.

activity-frames enforces a two-tier contract (SPEC.md):

  • Tier 1, measured (this package): everything is derivable by deterministic code from capture data - sessions, durations, typed page entities, input volume, coverage gaps. No interpretation, no intent labels. Same input, same output, every time.
  • Tier 2, inferred (optional extension): tools that add interpretation must namespace it, tag confidence (high | medium | speculative), and link evidence. Facts and guesses can never silently mix.

Every frame carries evidence pointers back to raw capture rows. Every document declares its blind spots. What the system did not see, it says it did not see.

Use it from an agent (MCP)

# Claude Code
claude mcp add activity-frames -- aframes mcp

Any MCP client works: command aframes, args ["mcp"]. Six tools: get_context, get_activity, get_steps (expand one activity frame into its ordered click-by-click script - the replay view of a demonstrated run, so an agent can repeat the task instead of re-deriving it), get_day_summary, get_patterns (repetitive-workflow detection: repeated clicks, action sequences, URL patterns, app-switching loops, daily habits), and get_communications (email/messaging surfaces with the window titles seen on each — for many clients the title carries the subject or conversation name; a client that doesn't title its windows with the conversation leaves only its presence to report. Titles only, measured tier: message bodies are never read).

Use it from Python

fromactivity_framesimportActivityLoglog=ActivityLog()
doc=log.day() # today, structureddoc=log.recent(hours=2) # last 2 hoursprint(log.context(hours=2)) # paste-ready context block

Privacy model

  • Local only. Capture, storage, and compilation all happen on your machine. Nothing is uploaded anywhere, ever.
  • Read-only compilation. The compiler opens the capture database read-only.
  • Content opt-in at the output. Compiled documents carry input counts by default; typed-text content appears only if you explicitly pass --include-text (this also gates the repeated-text pattern detector). Be clear about the boundary: the capture database itself does store what the recorder sees, locally, so protect it like any sensitive file (FileVault, permissions).
  • Audio off by default.aframes record --audio to opt in.
  • No LLM in the compile path. Compilation is plain code, so no language model, local or remote, is involved in producing memory. The capture engine does run on-device OCR to read what is on screen; that stays on your machine.
  • You choose what leaves, when you paste a context block into an agent. Note that window titles and page entities originate from your screen and can contain third-party text; agents should treat them as data, not instructions.

Architecture

 capture engine compiler (this package) your agent
------------------ --------------------------- -----------------
screen snapshots --> sessionize (dwell, gaps, --> MCP tools /
accessibility tree flicker merge) context blocks /
input events entity typing (25+ sites) JSON, YAML, md
(local SQLite) enrichment, patterns

The default capture engine is nocta-recorder: aframes record provisions a pinned, MIT-licensed build, verifies its published sha256 before first run, and manages it for you (see ACKNOWLEDGMENTS.md); a nocta-recorder binary already on your PATH is used as-is (bring your own build). Already running your own recorder? Point $AFRAMES_DB at any capture database with compatible frames / ui_events / elements tables and skip aframes record entirely.

CLI

aframes record # start capture (--stop / --status / --audio)
aframes today # today's frames (YAML*)
aframes day 2026-07-03 -f json # any day, JSON
aframes context --hours 3 # agent context block
aframes apps # per-app time ledger
aframes patterns --days 7 # repetitive workflow detection
aframes comms --hours 24 # email/messaging surfaces + titles seen
aframes steps --frame f-0002 # one frame's click-by-click script (or --find "task query")
aframes mcp # MCP stdio server

*YAML output uses PyYAML (pip install "activity-frames[yaml]"); without it the CLI falls back to JSON.

Run aframes <cmd> --help for the full flag set (--db, --include-text, --layout, ...).

Docs

AGENTS.mdIntegration guide for AI agents (install, MCP tools, replay loop, cautions)
SPEC.mdThe schema contract: measured vs inferred tiers
docs/mcp.mdMCP setup and the six tools in detail
docs/python.mdPython API reference
docs/cli.mdEvery command and flag
docs/troubleshooting.mdKeyed by the actual error you see
research/The cost instrument, measurements, and replay executor behind the paper
llms.txtCompressed repo map for LLM context windows

Status

v0.2. Developed and tested on macOS (Apple Silicon); an Intel macOS engine build is published but less exercised - reports welcome. No prebuilt Linux engine yet: on Linux, run your own recorder and point $AFRAMES_DB at its database (the compiler itself is tested on Linux in CI). Entity parsers cover LinkedIn, GitHub, GitLab, Google (Search/Docs/Gmail/Maps/Meet/Calendar), YouTube, X, Instagram, Reddit, Luma, Partiful, Product Hunt, Vercel, Supabase, Stripe (dashboard), Discord, Slack, Notion, Figma, Linear, Stack Overflow, Calendly, Crunchbase, Atlassian (Jira/Confluence), ChatGPT/Claude, localhost; unknown sites fall back to a generic page reference - always total, never lossy. Issues and parser PRs welcome.

Paper

Activity Frames: Deterministic Screen-Activity Compilation for Agent Memory and Replay
Nossa Iyamu, arXiv 2026

arXivHugging Face

@misc{iyamu2026activityframes,
title={Activity Frames: Deterministic Screen-Activity Compilation for Agent Memory and Replay},
author={Nossa Iyamu},
year={2026},
eprint={2608.05784},
archivePrefix={arXiv},
primaryClass={cs.AI},
url={https://arxiv.org/abs/2608.05784}
}

Built by Nossa Iyamu, maker of Nocta. MIT.

About

Turn your workday into structured workflows agents can execute. 100% local, served over MCP.

Topics

Resources

Contributing

Security policy

Stars

541 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

activity-frames powering Nocta

arXivHackerNoonHugging FacePyPIDownloadsDownloads/monthGitHub starsPythonMCPtestsLicense: MITWebsite

Turn your workday into structured workflows agents can execute.

Computer-use agents work every task out from scratch, even one you've done a hundred times. And between tasks, your agent has no idea what you've been doing all day, so it starts every conversation blind.

activity-frames fixes both. It records your screen locally and compiles what it sees into structured activity frames: bounded, deterministic records of the tasks you actually did. The recurring ones become workflows an agent can execute instead of working out again. So your repetitive computer tasks get done cheaper (running a compiled workflow costs almost no tokens) and more reliable (the same steps, grounded the same way every time, instead of guessing from a screenshot) - and everything else becomes context your agent can use.

pip install activity-frames
aframes record # start capturing (local, audio off by default)
aframes context # your last 2 hours, agent-ready

Want to go deeper?docs/mcp.md covers the six MCP tools, docs/python.md the Python API, docs/cli.md every flag, docs/troubleshooting.md the errors you might actually hit. AI agent integrating this? AGENTS.md is written for you.

What your agent sees

Capture stores instants: thousands of snapshot rows a day, each one saying "at 22:53:05, Chrome showed linkedin.com/in/...". Useless to reason over.

activity-frames compiles those instants into activity frames:

- id: f-0007app: Google Chromesite: linkedin.comstart: "20:24:04"end: "20:42:11"duration_min: 18.0pages:
- {kind: people_search, entity: "cto berlin", count: 2}
- {kind: profile, entity: john-doe}
- {kind: company, entity: acme-ai}input: {keys: 214, clicks: 31}evidence: {frame_ids: "99871..100147"}

And into a compact context block for any system prompt:

USER ACTIVITY (2026-08-10, local time; measured from screen capture, no interpretation):
coverage: 10:29-22:55, 529 active min, 15 apps
away: 13:02-14:39 (97m)
- 10:54-11:23 Google Chrome/app.cal.com (28.7m): dashboard:video x59
- 12:49-12:52 cmux (2.7m): ⠂ Build nocta-recorder ground-up screen recorder
- 12:52-13:02 Google Chrome/calendar.google.com (9.7m): calendar x20
- 20:53-20:56 Google Chrome/github.com (3.3m): user:bethvourc x5; repo:nossa-y/activity-frames x2
- 20:56-21:01 Google Chrome/hackernoon.com (5.2m): page:i-compiled-55-days-of-screen-activity-into-episodic-memory-for-my-ai-agent x12

That is a real day of mine (excerpt; the full block has 44 frames). It compiles to 1,371 tokens; the raw snapshot rows for the same day are 247,563. Compiles in under a second, costs zero tokens, no LLM in the loop.

Drop it into a prompt and your agent knows your day - ask "what was that article I had open around 9pm?" and it answers in one line, off the block alone:

aframes demo: compile a real day, ask the agent, get a one-line answer

Real exchange, MCP disabled - the answer comes from the compiled block only.

Workflows agents can execute

Computer-use agents re-derive every task from scratch - screenshot, reason, act, repeat - even for a workflow they've run a hundred times. That re-derivation is where the token cost goes, and it's waste: the workflow hasn't changed.

Because activity-frames compiles recurring activity deterministically, a task you've demonstrated becomes an executable script:

aframes steps --find "message john doe"
{
"steps": [
{"t": "20:24:09", "op": "focus", "target": "Google Chrome · LinkedIn", "n": 1},
{"t": "20:24:14", "op": "click", "target": "Search", "role": "TextField", "url": "https://www.linkedin.com/feed/", "n": 2},
{"t": "20:24:16", "op": "type", "chars": 8, "text": "john doe", "n": 3},
{"t": "20:24:21", "op": "click", "target": "John Doe", "role": "Link", "url": "https://www.linkedin.com/search/results/people/", "n": 4},
{"t": "20:24:29", "op": "click", "target": "Message", "role": "Button", "url": "https://www.linkedin.com/in/john-doe/", "n": 5},
{"t": "20:24:35", "op": "type", "chars": 71, "text": "hey, loved your post on agent memory - open to a quick chat next week?", "n": 6}
],
"step_count": 6,
"unresolved_clicks": 0
}

That's the replay view of a demonstrated run - ordered clicks grounded by element name and role, typed runs, focus changes. An agent repeats the task instead of re-deriving it: fill the slots with new values (a different name, the same steps) and execute. On the happy path it replays at zero model calls; anything unexpected halts and asks instead of guessing.

We measured how much agents overpay to re-derive workflows they've already performed - the Routine Overhead Ratio - on weeks of real activity, replicated it on a public web-task dataset, and built a deterministic executor that replays a compiled workflow in a real browser. Instrument, measurements, and executor: research/.

Passively-captured activity becomes deterministic action - and the cheapest computer task is the one an agent never reasons through twice.

Measured, not guessed

Agent memory today means conversation memory: what you told the model. What you actually did is the missing half - and the hard part is representing it without lying.

activity-frames enforces a two-tier contract (SPEC.md):

  • Tier 1, measured (this package): everything is derivable by deterministic code from capture data - sessions, durations, typed page entities, input volume, coverage gaps. No interpretation, no intent labels. Same input, same output, every time.
  • Tier 2, inferred (optional extension): tools that add interpretation must namespace it, tag confidence (high | medium | speculative), and link evidence. Facts and guesses can never silently mix.

Every frame carries evidence pointers back to raw capture rows. Every document declares its blind spots. What the system did not see, it says it did not see.

Use it from an agent (MCP)

# Claude Code
claude mcp add activity-frames -- aframes mcp

Any MCP client works: command aframes, args ["mcp"]. Six tools: get_context, get_activity, get_steps (expand one activity frame into its ordered click-by-click script - the replay view of a demonstrated run, so an agent can repeat the task instead of re-deriving it), get_day_summary, get_patterns (repetitive-workflow detection: repeated clicks, action sequences, URL patterns, app-switching loops, daily habits), and get_communications (email/messaging surfaces with the window titles seen on each — for many clients the title carries the subject or conversation name; a client that doesn't title its windows with the conversation leaves only its presence to report. Titles only, measured tier: message bodies are never read).

Use it from Python

fromactivity_framesimportActivityLoglog=ActivityLog()
doc=log.day() # today, structureddoc=log.recent(hours=2) # last 2 hoursprint(log.context(hours=2)) # paste-ready context block

Privacy model

  • Local only. Capture, storage, and compilation all happen on your machine. Nothing is uploaded anywhere, ever.
  • Read-only compilation. The compiler opens the capture database read-only.
  • Content opt-in at the output. Compiled documents carry input counts by default; typed-text content appears only if you explicitly pass --include-text (this also gates the repeated-text pattern detector). Be clear about the boundary: the capture database itself does store what the recorder sees, locally, so protect it like any sensitive file (FileVault, permissions).
  • Audio off by default.aframes record --audio to opt in.
  • No LLM in the compile path. Compilation is plain code, so no language model, local or remote, is involved in producing memory. The capture engine does run on-device OCR to read what is on screen; that stays on your machine.
  • You choose what leaves, when you paste a context block into an agent. Note that window titles and page entities originate from your screen and can contain third-party text; agents should treat them as data, not instructions.

Architecture

 capture engine compiler (this package) your agent
------------------ --------------------------- -----------------
screen snapshots --> sessionize (dwell, gaps, --> MCP tools /
accessibility tree flicker merge) context blocks /
input events entity typing (25+ sites) JSON, YAML, md
(local SQLite) enrichment, patterns

The default capture engine is nocta-recorder: aframes record provisions a pinned, MIT-licensed build, verifies its published sha256 before first run, and manages it for you (see ACKNOWLEDGMENTS.md); a nocta-recorder binary already on your PATH is used as-is (bring your own build). Already running your own recorder? Point $AFRAMES_DB at any capture database with compatible frames / ui_events / elements tables and skip aframes record entirely.

CLI

aframes record # start capture (--stop / --status / --audio)
aframes today # today's frames (YAML*)
aframes day 2026-07-03 -f json # any day, JSON
aframes context --hours 3 # agent context block
aframes apps # per-app time ledger
aframes patterns --days 7 # repetitive workflow detection
aframes comms --hours 24 # email/messaging surfaces + titles seen
aframes steps --frame f-0002 # one frame's click-by-click script (or --find "task query")
aframes mcp # MCP stdio server

*YAML output uses PyYAML (pip install "activity-frames[yaml]"); without it the CLI falls back to JSON.

Run aframes <cmd> --help for the full flag set (--db, --include-text, --layout, ...).

Docs

AGENTS.mdIntegration guide for AI agents (install, MCP tools, replay loop, cautions)
SPEC.mdThe schema contract: measured vs inferred tiers
docs/mcp.mdMCP setup and the six tools in detail
docs/python.mdPython API reference
docs/cli.mdEvery command and flag
docs/troubleshooting.mdKeyed by the actual error you see
research/The cost instrument, measurements, and replay executor behind the paper
llms.txtCompressed repo map for LLM context windows

Status

v0.2. Developed and tested on macOS (Apple Silicon); an Intel macOS engine build is published but less exercised - reports welcome. No prebuilt Linux engine yet: on Linux, run your own recorder and point $AFRAMES_DB at its database (the compiler itself is tested on Linux in CI). Entity parsers cover LinkedIn, GitHub, GitLab, Google (Search/Docs/Gmail/Maps/Meet/Calendar), YouTube, X, Instagram, Reddit, Luma, Partiful, Product Hunt, Vercel, Supabase, Stripe (dashboard), Discord, Slack, Notion, Figma, Linear, Stack Overflow, Calendly, Crunchbase, Atlassian (Jira/Confluence), ChatGPT/Claude, localhost; unknown sites fall back to a generic page reference - always total, never lossy. Issues and parser PRs welcome.

Paper

Activity Frames: Deterministic Screen-Activity Compilation for Agent Memory and Replay
Nossa Iyamu, arXiv 2026

arXivHugging Face

@misc{iyamu2026activityframes,
title={Activity Frames: Deterministic Screen-Activity Compilation for Agent Memory and Replay},
author={Nossa Iyamu},
year={2026},
eprint={2608.05784},
archivePrefix={arXiv},
primaryClass={cs.AI},
url={https://arxiv.org/abs/2608.05784}
}

Built by Nossa Iyamu, maker of Nocta. MIT.

About

Turn your workday into structured workflows agents can execute. 100% local, served over MCP.

Topics

Resources

Contributing

Security policy

Stars

541 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

activity-frames powering Nocta

arXivHackerNoonHugging FacePyPIDownloadsDownloads/monthGitHub starsPythonMCPtestsLicense: MITWebsite

Turn your workday into structured workflows agents can execute.

Computer-use agents work every task out from scratch, even one you've done a hundred times. And between tasks, your agent has no idea what you've been doing all day, so it starts every conversation blind.

activity-frames fixes both. It records your screen locally and compiles what it sees into structured activity frames: bounded, deterministic records of the tasks you actually did. The recurring ones become workflows an agent can execute instead of working out again. So your repetitive computer tasks get done cheaper (running a compiled workflow costs almost no tokens) and more reliable (the same steps, grounded the same way every time, instead of guessing from a screenshot) - and everything else becomes context your agent can use.

pip install activity-frames
aframes record # start capturing (local, audio off by default)
aframes context # your last 2 hours, agent-ready

Want to go deeper?docs/mcp.md covers the six MCP tools, docs/python.md the Python API, docs/cli.md every flag, docs/troubleshooting.md the errors you might actually hit. AI agent integrating this? AGENTS.md is written for you.

What your agent sees

Capture stores instants: thousands of snapshot rows a day, each one saying "at 22:53:05, Chrome showed linkedin.com/in/...". Useless to reason over.

activity-frames compiles those instants into activity frames:

- id: f-0007app: Google Chromesite: linkedin.comstart: "20:24:04"end: "20:42:11"duration_min: 18.0pages:
- {kind: people_search, entity: "cto berlin", count: 2}
- {kind: profile, entity: john-doe}
- {kind: company, entity: acme-ai}input: {keys: 214, clicks: 31}evidence: {frame_ids: "99871..100147"}

And into a compact context block for any system prompt:

USER ACTIVITY (2026-08-10, local time; measured from screen capture, no interpretation):
coverage: 10:29-22:55, 529 active min, 15 apps
away: 13:02-14:39 (97m)
- 10:54-11:23 Google Chrome/app.cal.com (28.7m): dashboard:video x59
- 12:49-12:52 cmux (2.7m): ⠂ Build nocta-recorder ground-up screen recorder
- 12:52-13:02 Google Chrome/calendar.google.com (9.7m): calendar x20
- 20:53-20:56 Google Chrome/github.com (3.3m): user:bethvourc x5; repo:nossa-y/activity-frames x2
- 20:56-21:01 Google Chrome/hackernoon.com (5.2m): page:i-compiled-55-days-of-screen-activity-into-episodic-memory-for-my-ai-agent x12

That is a real day of mine (excerpt; the full block has 44 frames). It compiles to 1,371 tokens; the raw snapshot rows for the same day are 247,563. Compiles in under a second, costs zero tokens, no LLM in the loop.

Drop it into a prompt and your agent knows your day - ask "what was that article I had open around 9pm?" and it answers in one line, off the block alone:

aframes demo: compile a real day, ask the agent, get a one-line answer

Real exchange, MCP disabled - the answer comes from the compiled block only.

Workflows agents can execute

Computer-use agents re-derive every task from scratch - screenshot, reason, act, repeat - even for a workflow they've run a hundred times. That re-derivation is where the token cost goes, and it's waste: the workflow hasn't changed.

Because activity-frames compiles recurring activity deterministically, a task you've demonstrated becomes an executable script:

aframes steps --find "message john doe"
{
"steps": [
{"t": "20:24:09", "op": "focus", "target": "Google Chrome · LinkedIn", "n": 1},
{"t": "20:24:14", "op": "click", "target": "Search", "role": "TextField", "url": "https://www.linkedin.com/feed/", "n": 2},
{"t": "20:24:16", "op": "type", "chars": 8, "text": "john doe", "n": 3},
{"t": "20:24:21", "op": "click", "target": "John Doe", "role": "Link", "url": "https://www.linkedin.com/search/results/people/", "n": 4},
{"t": "20:24:29", "op": "click", "target": "Message", "role": "Button", "url": "https://www.linkedin.com/in/john-doe/", "n": 5},
{"t": "20:24:35", "op": "type", "chars": 71, "text": "hey, loved your post on agent memory - open to a quick chat next week?", "n": 6}
],
"step_count": 6,
"unresolved_clicks": 0
}

That's the replay view of a demonstrated run - ordered clicks grounded by element name and role, typed runs, focus changes. An agent repeats the task instead of re-deriving it: fill the slots with new values (a different name, the same steps) and execute. On the happy path it replays at zero model calls; anything unexpected halts and asks instead of guessing.

We measured how much agents overpay to re-derive workflows they've already performed - the Routine Overhead Ratio - on weeks of real activity, replicated it on a public web-task dataset, and built a deterministic executor that replays a compiled workflow in a real browser. Instrument, measurements, and executor: research/.

Passively-captured activity becomes deterministic action - and the cheapest computer task is the one an agent never reasons through twice.

Measured, not guessed

Agent memory today means conversation memory: what you told the model. What you actually did is the missing half - and the hard part is representing it without lying.

activity-frames enforces a two-tier contract (SPEC.md):

  • Tier 1, measured (this package): everything is derivable by deterministic code from capture data - sessions, durations, typed page entities, input volume, coverage gaps. No interpretation, no intent labels. Same input, same output, every time.
  • Tier 2, inferred (optional extension): tools that add interpretation must namespace it, tag confidence (high | medium | speculative), and link evidence. Facts and guesses can never silently mix.

Every frame carries evidence pointers back to raw capture rows. Every document declares its blind spots. What the system did not see, it says it did not see.

Use it from an agent (MCP)

# Claude Code
claude mcp add activity-frames -- aframes mcp

Any MCP client works: command aframes, args ["mcp"]. Six tools: get_context, get_activity, get_steps (expand one activity frame into its ordered click-by-click script - the replay view of a demonstrated run, so an agent can repeat the task instead of re-deriving it), get_day_summary, get_patterns (repetitive-workflow detection: repeated clicks, action sequences, URL patterns, app-switching loops, daily habits), and get_communications (email/messaging surfaces with the window titles seen on each — for many clients the title carries the subject or conversation name; a client that doesn't title its windows with the conversation leaves only its presence to report. Titles only, measured tier: message bodies are never read).

Use it from Python

fromactivity_framesimportActivityLoglog=ActivityLog()
doc=log.day() # today, structureddoc=log.recent(hours=2) # last 2 hoursprint(log.context(hours=2)) # paste-ready context block

Privacy model

  • Local only. Capture, storage, and compilation all happen on your machine. Nothing is uploaded anywhere, ever.
  • Read-only compilation. The compiler opens the capture database read-only.
  • Content opt-in at the output. Compiled documents carry input counts by default; typed-text content appears only if you explicitly pass --include-text (this also gates the repeated-text pattern detector). Be clear about the boundary: the capture database itself does store what the recorder sees, locally, so protect it like any sensitive file (FileVault, permissions).
  • Audio off by default.aframes record --audio to opt in.
  • No LLM in the compile path. Compilation is plain code, so no language model, local or remote, is involved in producing memory. The capture engine does run on-device OCR to read what is on screen; that stays on your machine.
  • You choose what leaves, when you paste a context block into an agent. Note that window titles and page entities originate from your screen and can contain third-party text; agents should treat them as data, not instructions.

Architecture

 capture engine compiler (this package) your agent
------------------ --------------------------- -----------------
screen snapshots --> sessionize (dwell, gaps, --> MCP tools /
accessibility tree flicker merge) context blocks /
input events entity typing (25+ sites) JSON, YAML, md
(local SQLite) enrichment, patterns

The default capture engine is nocta-recorder: aframes record provisions a pinned, MIT-licensed build, verifies its published sha256 before first run, and manages it for you (see ACKNOWLEDGMENTS.md); a nocta-recorder binary already on your PATH is used as-is (bring your own build). Already running your own recorder? Point $AFRAMES_DB at any capture database with compatible frames / ui_events / elements tables and skip aframes record entirely.

CLI

aframes record # start capture (--stop / --status / --audio)
aframes today # today's frames (YAML*)
aframes day 2026-07-03 -f json # any day, JSON
aframes context --hours 3 # agent context block
aframes apps # per-app time ledger
aframes patterns --days 7 # repetitive workflow detection
aframes comms --hours 24 # email/messaging surfaces + titles seen
aframes steps --frame f-0002 # one frame's click-by-click script (or --find "task query")
aframes mcp # MCP stdio server

*YAML output uses PyYAML (pip install "activity-frames[yaml]"); without it the CLI falls back to JSON.

Run aframes <cmd> --help for the full flag set (--db, --include-text, --layout, ...).

Docs

AGENTS.mdIntegration guide for AI agents (install, MCP tools, replay loop, cautions)
SPEC.mdThe schema contract: measured vs inferred tiers
docs/mcp.mdMCP setup and the six tools in detail
docs/python.mdPython API reference
docs/cli.mdEvery command and flag
docs/troubleshooting.mdKeyed by the actual error you see
research/The cost instrument, measurements, and replay executor behind the paper
llms.txtCompressed repo map for LLM context windows

Status

v0.2. Developed and tested on macOS (Apple Silicon); an Intel macOS engine build is published but less exercised - reports welcome. No prebuilt Linux engine yet: on Linux, run your own recorder and point $AFRAMES_DB at its database (the compiler itself is tested on Linux in CI). Entity parsers cover LinkedIn, GitHub, GitLab, Google (Search/Docs/Gmail/Maps/Meet/Calendar), YouTube, X, Instagram, Reddit, Luma, Partiful, Product Hunt, Vercel, Supabase, Stripe (dashboard), Discord, Slack, Notion, Figma, Linear, Stack Overflow, Calendly, Crunchbase, Atlassian (Jira/Confluence), ChatGPT/Claude, localhost; unknown sites fall back to a generic page reference - always total, never lossy. Issues and parser PRs welcome.

Paper

Activity Frames: Deterministic Screen-Activity Compilation for Agent Memory and Replay
Nossa Iyamu, arXiv 2026

arXivHugging Face

@misc{iyamu2026activityframes,
title={Activity Frames: Deterministic Screen-Activity Compilation for Agent Memory and Replay},
author={Nossa Iyamu},
year={2026},
eprint={2608.05784},
archivePrefix={arXiv},
primaryClass={cs.AI},
url={https://arxiv.org/abs/2608.05784}
}

Built by Nossa Iyamu, maker of Nocta. MIT.

About

Turn your workday into structured workflows agents can execute. 100% local, served over MCP.

Topics

Resources

Contributing

Security policy

Stars

541 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

activity-frames powering Nocta

arXivHackerNoonHugging FacePyPIDownloadsDownloads/monthGitHub starsPythonMCPtestsLicense: MITWebsite

Turn your workday into structured workflows agents can execute.

Computer-use agents work every task out from scratch, even one you've done a hundred times. And between tasks, your agent has no idea what you've been doing all day, so it starts every conversation blind.

activity-frames fixes both. It records your screen locally and compiles what it sees into structured activity frames: bounded, deterministic records of the tasks you actually did. The recurring ones become workflows an agent can execute instead of working out again. So your repetitive computer tasks get done cheaper (running a compiled workflow costs almost no tokens) and more reliable (the same steps, grounded the same way every time, instead of guessing from a screenshot) - and everything else becomes context your agent can use.

pip install activity-frames
aframes record # start capturing (local, audio off by default)
aframes context # your last 2 hours, agent-ready

Want to go deeper?docs/mcp.md covers the six MCP tools, docs/python.md the Python API, docs/cli.md every flag, docs/troubleshooting.md the errors you might actually hit. AI agent integrating this? AGENTS.md is written for you.

What your agent sees

Capture stores instants: thousands of snapshot rows a day, each one saying "at 22:53:05, Chrome showed linkedin.com/in/...". Useless to reason over.

activity-frames compiles those instants into activity frames:

- id: f-0007app: Google Chromesite: linkedin.comstart: "20:24:04"end: "20:42:11"duration_min: 18.0pages:
- {kind: people_search, entity: "cto berlin", count: 2}
- {kind: profile, entity: john-doe}
- {kind: company, entity: acme-ai}input: {keys: 214, clicks: 31}evidence: {frame_ids: "99871..100147"}

And into a compact context block for any system prompt:

USER ACTIVITY (2026-08-10, local time; measured from screen capture, no interpretation):
coverage: 10:29-22:55, 529 active min, 15 apps
away: 13:02-14:39 (97m)
- 10:54-11:23 Google Chrome/app.cal.com (28.7m): dashboard:video x59
- 12:49-12:52 cmux (2.7m): ⠂ Build nocta-recorder ground-up screen recorder
- 12:52-13:02 Google Chrome/calendar.google.com (9.7m): calendar x20
- 20:53-20:56 Google Chrome/github.com (3.3m): user:bethvourc x5; repo:nossa-y/activity-frames x2
- 20:56-21:01 Google Chrome/hackernoon.com (5.2m): page:i-compiled-55-days-of-screen-activity-into-episodic-memory-for-my-ai-agent x12

That is a real day of mine (excerpt; the full block has 44 frames). It compiles to 1,371 tokens; the raw snapshot rows for the same day are 247,563. Compiles in under a second, costs zero tokens, no LLM in the loop.

Drop it into a prompt and your agent knows your day - ask "what was that article I had open around 9pm?" and it answers in one line, off the block alone:

aframes demo: compile a real day, ask the agent, get a one-line answer

Real exchange, MCP disabled - the answer comes from the compiled block only.

Workflows agents can execute

Computer-use agents re-derive every task from scratch - screenshot, reason, act, repeat - even for a workflow they've run a hundred times. That re-derivation is where the token cost goes, and it's waste: the workflow hasn't changed.

Because activity-frames compiles recurring activity deterministically, a task you've demonstrated becomes an executable script:

aframes steps --find "message john doe"
{
"steps": [
{"t": "20:24:09", "op": "focus", "target": "Google Chrome · LinkedIn", "n": 1},
{"t": "20:24:14", "op": "click", "target": "Search", "role": "TextField", "url": "https://www.linkedin.com/feed/", "n": 2},
{"t": "20:24:16", "op": "type", "chars": 8, "text": "john doe", "n": 3},
{"t": "20:24:21", "op": "click", "target": "John Doe", "role": "Link", "url": "https://www.linkedin.com/search/results/people/", "n": 4},
{"t": "20:24:29", "op": "click", "target": "Message", "role": "Button", "url": "https://www.linkedin.com/in/john-doe/", "n": 5},
{"t": "20:24:35", "op": "type", "chars": 71, "text": "hey, loved your post on agent memory - open to a quick chat next week?", "n": 6}
],
"step_count": 6,
"unresolved_clicks": 0
}

That's the replay view of a demonstrated run - ordered clicks grounded by element name and role, typed runs, focus changes. An agent repeats the task instead of re-deriving it: fill the slots with new values (a different name, the same steps) and execute. On the happy path it replays at zero model calls; anything unexpected halts and asks instead of guessing.

We measured how much agents overpay to re-derive workflows they've already performed - the Routine Overhead Ratio - on weeks of real activity, replicated it on a public web-task dataset, and built a deterministic executor that replays a compiled workflow in a real browser. Instrument, measurements, and executor: research/.

Passively-captured activity becomes deterministic action - and the cheapest computer task is the one an agent never reasons through twice.

Measured, not guessed

Agent memory today means conversation memory: what you told the model. What you actually did is the missing half - and the hard part is representing it without lying.

activity-frames enforces a two-tier contract (SPEC.md):

  • Tier 1, measured (this package): everything is derivable by deterministic code from capture data - sessions, durations, typed page entities, input volume, coverage gaps. No interpretation, no intent labels. Same input, same output, every time.
  • Tier 2, inferred (optional extension): tools that add interpretation must namespace it, tag confidence (high | medium | speculative), and link evidence. Facts and guesses can never silently mix.

Every frame carries evidence pointers back to raw capture rows. Every document declares its blind spots. What the system did not see, it says it did not see.

Use it from an agent (MCP)

# Claude Code
claude mcp add activity-frames -- aframes mcp

Any MCP client works: command aframes, args ["mcp"]. Six tools: get_context, get_activity, get_steps (expand one activity frame into its ordered click-by-click script - the replay view of a demonstrated run, so an agent can repeat the task instead of re-deriving it), get_day_summary, get_patterns (repetitive-workflow detection: repeated clicks, action sequences, URL patterns, app-switching loops, daily habits), and get_communications (email/messaging surfaces with the window titles seen on each — for many clients the title carries the subject or conversation name; a client that doesn't title its windows with the conversation leaves only its presence to report. Titles only, measured tier: message bodies are never read).

Use it from Python

fromactivity_framesimportActivityLoglog=ActivityLog()
doc=log.day() # today, structureddoc=log.recent(hours=2) # last 2 hoursprint(log.context(hours=2)) # paste-ready context block

Privacy model

  • Local only. Capture, storage, and compilation all happen on your machine. Nothing is uploaded anywhere, ever.
  • Read-only compilation. The compiler opens the capture database read-only.
  • Content opt-in at the output. Compiled documents carry input counts by default; typed-text content appears only if you explicitly pass --include-text (this also gates the repeated-text pattern detector). Be clear about the boundary: the capture database itself does store what the recorder sees, locally, so protect it like any sensitive file (FileVault, permissions).
  • Audio off by default.aframes record --audio to opt in.
  • No LLM in the compile path. Compilation is plain code, so no language model, local or remote, is involved in producing memory. The capture engine does run on-device OCR to read what is on screen; that stays on your machine.
  • You choose what leaves, when you paste a context block into an agent. Note that window titles and page entities originate from your screen and can contain third-party text; agents should treat them as data, not instructions.

Architecture

 capture engine compiler (this package) your agent
------------------ --------------------------- -----------------
screen snapshots --> sessionize (dwell, gaps, --> MCP tools /
accessibility tree flicker merge) context blocks /
input events entity typing (25+ sites) JSON, YAML, md
(local SQLite) enrichment, patterns

The default capture engine is nocta-recorder: aframes record provisions a pinned, MIT-licensed build, verifies its published sha256 before first run, and manages it for you (see ACKNOWLEDGMENTS.md); a nocta-recorder binary already on your PATH is used as-is (bring your own build). Already running your own recorder? Point $AFRAMES_DB at any capture database with compatible frames / ui_events / elements tables and skip aframes record entirely.

CLI

aframes record # start capture (--stop / --status / --audio)
aframes today # today's frames (YAML*)
aframes day 2026-07-03 -f json # any day, JSON
aframes context --hours 3 # agent context block
aframes apps # per-app time ledger
aframes patterns --days 7 # repetitive workflow detection
aframes comms --hours 24 # email/messaging surfaces + titles seen
aframes steps --frame f-0002 # one frame's click-by-click script (or --find "task query")
aframes mcp # MCP stdio server

*YAML output uses PyYAML (pip install "activity-frames[yaml]"); without it the CLI falls back to JSON.

Run aframes <cmd> --help for the full flag set (--db, --include-text, --layout, ...).

Docs

AGENTS.mdIntegration guide for AI agents (install, MCP tools, replay loop, cautions)
SPEC.mdThe schema contract: measured vs inferred tiers
docs/mcp.mdMCP setup and the six tools in detail
docs/python.mdPython API reference
docs/cli.mdEvery command and flag
docs/troubleshooting.mdKeyed by the actual error you see
research/The cost instrument, measurements, and replay executor behind the paper
llms.txtCompressed repo map for LLM context windows

Status

v0.2. Developed and tested on macOS (Apple Silicon); an Intel macOS engine build is published but less exercised - reports welcome. No prebuilt Linux engine yet: on Linux, run your own recorder and point $AFRAMES_DB at its database (the compiler itself is tested on Linux in CI). Entity parsers cover LinkedIn, GitHub, GitLab, Google (Search/Docs/Gmail/Maps/Meet/Calendar), YouTube, X, Instagram, Reddit, Luma, Partiful, Product Hunt, Vercel, Supabase, Stripe (dashboard), Discord, Slack, Notion, Figma, Linear, Stack Overflow, Calendly, Crunchbase, Atlassian (Jira/Confluence), ChatGPT/Claude, localhost; unknown sites fall back to a generic page reference - always total, never lossy. Issues and parser PRs welcome.

Paper

Activity Frames: Deterministic Screen-Activity Compilation for Agent Memory and Replay
Nossa Iyamu, arXiv 2026

arXivHugging Face

@misc{iyamu2026activityframes,
title={Activity Frames: Deterministic Screen-Activity Compilation for Agent Memory and Replay},
author={Nossa Iyamu},
year={2026},
eprint={2608.05784},
archivePrefix={arXiv},
primaryClass={cs.AI},
url={https://arxiv.org/abs/2608.05784}
}

Built by Nossa Iyamu, maker of Nocta. MIT.

About

Turn your workday into structured workflows agents can execute. 100% local, served over MCP.

Topics

Resources

Contributing

Security policy

Stars

541 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

activity-frames powering Nocta

arXivHackerNoonHugging FacePyPIDownloadsDownloads/monthGitHub starsPythonMCPtestsLicense: MITWebsite

Turn your workday into structured workflows agents can execute.

Computer-use agents work every task out from scratch, even one you've done a hundred times. And between tasks, your agent has no idea what you've been doing all day, so it starts every conversation blind.

activity-frames fixes both. It records your screen locally and compiles what it sees into structured activity frames: bounded, deterministic records of the tasks you actually did. The recurring ones become workflows an agent can execute instead of working out again. So your repetitive computer tasks get done cheaper (running a compiled workflow costs almost no tokens) and more reliable (the same steps, grounded the same way every time, instead of guessing from a screenshot) - and everything else becomes context your agent can use.

pip install activity-frames
aframes record # start capturing (local, audio off by default)
aframes context # your last 2 hours, agent-ready

Want to go deeper?docs/mcp.md covers the six MCP tools, docs/python.md the Python API, docs/cli.md every flag, docs/troubleshooting.md the errors you might actually hit. AI agent integrating this? AGENTS.md is written for you.

What your agent sees

Capture stores instants: thousands of snapshot rows a day, each one saying "at 22:53:05, Chrome showed linkedin.com/in/...". Useless to reason over.

activity-frames compiles those instants into activity frames:

- id: f-0007app: Google Chromesite: linkedin.comstart: "20:24:04"end: "20:42:11"duration_min: 18.0pages:
- {kind: people_search, entity: "cto berlin", count: 2}
- {kind: profile, entity: john-doe}
- {kind: company, entity: acme-ai}input: {keys: 214, clicks: 31}evidence: {frame_ids: "99871..100147"}

And into a compact context block for any system prompt:

USER ACTIVITY (2026-08-10, local time; measured from screen capture, no interpretation):
coverage: 10:29-22:55, 529 active min, 15 apps
away: 13:02-14:39 (97m)
- 10:54-11:23 Google Chrome/app.cal.com (28.7m): dashboard:video x59
- 12:49-12:52 cmux (2.7m): ⠂ Build nocta-recorder ground-up screen recorder
- 12:52-13:02 Google Chrome/calendar.google.com (9.7m): calendar x20
- 20:53-20:56 Google Chrome/github.com (3.3m): user:bethvourc x5; repo:nossa-y/activity-frames x2
- 20:56-21:01 Google Chrome/hackernoon.com (5.2m): page:i-compiled-55-days-of-screen-activity-into-episodic-memory-for-my-ai-agent x12

That is a real day of mine (excerpt; the full block has 44 frames). It compiles to 1,371 tokens; the raw snapshot rows for the same day are 247,563. Compiles in under a second, costs zero tokens, no LLM in the loop.

Drop it into a prompt and your agent knows your day - ask "what was that article I had open around 9pm?" and it answers in one line, off the block alone:

aframes demo: compile a real day, ask the agent, get a one-line answer

Real exchange, MCP disabled - the answer comes from the compiled block only.

Workflows agents can execute

Computer-use agents re-derive every task from scratch - screenshot, reason, act, repeat - even for a workflow they've run a hundred times. That re-derivation is where the token cost goes, and it's waste: the workflow hasn't changed.

Because activity-frames compiles recurring activity deterministically, a task you've demonstrated becomes an executable script:

aframes steps --find "message john doe"
{
"steps": [
{"t": "20:24:09", "op": "focus", "target": "Google Chrome · LinkedIn", "n": 1},
{"t": "20:24:14", "op": "click", "target": "Search", "role": "TextField", "url": "https://www.linkedin.com/feed/", "n": 2},
{"t": "20:24:16", "op": "type", "chars": 8, "text": "john doe", "n": 3},
{"t": "20:24:21", "op": "click", "target": "John Doe", "role": "Link", "url": "https://www.linkedin.com/search/results/people/", "n": 4},
{"t": "20:24:29", "op": "click", "target": "Message", "role": "Button", "url": "https://www.linkedin.com/in/john-doe/", "n": 5},
{"t": "20:24:35", "op": "type", "chars": 71, "text": "hey, loved your post on agent memory - open to a quick chat next week?", "n": 6}
],
"step_count": 6,
"unresolved_clicks": 0
}

That's the replay view of a demonstrated run - ordered clicks grounded by element name and role, typed runs, focus changes. An agent repeats the task instead of re-deriving it: fill the slots with new values (a different name, the same steps) and execute. On the happy path it replays at zero model calls; anything unexpected halts and asks instead of guessing.

We measured how much agents overpay to re-derive workflows they've already performed - the Routine Overhead Ratio - on weeks of real activity, replicated it on a public web-task dataset, and built a deterministic executor that replays a compiled workflow in a real browser. Instrument, measurements, and executor: research/.

Passively-captured activity becomes deterministic action - and the cheapest computer task is the one an agent never reasons through twice.

Measured, not guessed

Agent memory today means conversation memory: what you told the model. What you actually did is the missing half - and the hard part is representing it without lying.

activity-frames enforces a two-tier contract (SPEC.md):

  • Tier 1, measured (this package): everything is derivable by deterministic code from capture data - sessions, durations, typed page entities, input volume, coverage gaps. No interpretation, no intent labels. Same input, same output, every time.
  • Tier 2, inferred (optional extension): tools that add interpretation must namespace it, tag confidence (high | medium | speculative), and link evidence. Facts and guesses can never silently mix.

Every frame carries evidence pointers back to raw capture rows. Every document declares its blind spots. What the system did not see, it says it did not see.

Use it from an agent (MCP)

# Claude Code
claude mcp add activity-frames -- aframes mcp

Any MCP client works: command aframes, args ["mcp"]. Six tools: get_context, get_activity, get_steps (expand one activity frame into its ordered click-by-click script - the replay view of a demonstrated run, so an agent can repeat the task instead of re-deriving it), get_day_summary, get_patterns (repetitive-workflow detection: repeated clicks, action sequences, URL patterns, app-switching loops, daily habits), and get_communications (email/messaging surfaces with the window titles seen on each — for many clients the title carries the subject or conversation name; a client that doesn't title its windows with the conversation leaves only its presence to report. Titles only, measured tier: message bodies are never read).

Use it from Python

fromactivity_framesimportActivityLoglog=ActivityLog()
doc=log.day() # today, structureddoc=log.recent(hours=2) # last 2 hoursprint(log.context(hours=2)) # paste-ready context block

Privacy model

  • Local only. Capture, storage, and compilation all happen on your machine. Nothing is uploaded anywhere, ever.
  • Read-only compilation. The compiler opens the capture database read-only.
  • Content opt-in at the output. Compiled documents carry input counts by default; typed-text content appears only if you explicitly pass --include-text (this also gates the repeated-text pattern detector). Be clear about the boundary: the capture database itself does store what the recorder sees, locally, so protect it like any sensitive file (FileVault, permissions).
  • Audio off by default.aframes record --audio to opt in.
  • No LLM in the compile path. Compilation is plain code, so no language model, local or remote, is involved in producing memory. The capture engine does run on-device OCR to read what is on screen; that stays on your machine.
  • You choose what leaves, when you paste a context block into an agent. Note that window titles and page entities originate from your screen and can contain third-party text; agents should treat them as data, not instructions.

Architecture

 capture engine compiler (this package) your agent
------------------ --------------------------- -----------------
screen snapshots --> sessionize (dwell, gaps, --> MCP tools /
accessibility tree flicker merge) context blocks /
input events entity typing (25+ sites) JSON, YAML, md
(local SQLite) enrichment, patterns

The default capture engine is nocta-recorder: aframes record provisions a pinned, MIT-licensed build, verifies its published sha256 before first run, and manages it for you (see ACKNOWLEDGMENTS.md); a nocta-recorder binary already on your PATH is used as-is (bring your own build). Already running your own recorder? Point $AFRAMES_DB at any capture database with compatible frames / ui_events / elements tables and skip aframes record entirely.

CLI

aframes record # start capture (--stop / --status / --audio)
aframes today # today's frames (YAML*)
aframes day 2026-07-03 -f json # any day, JSON
aframes context --hours 3 # agent context block
aframes apps # per-app time ledger
aframes patterns --days 7 # repetitive workflow detection
aframes comms --hours 24 # email/messaging surfaces + titles seen
aframes steps --frame f-0002 # one frame's click-by-click script (or --find "task query")
aframes mcp # MCP stdio server

*YAML output uses PyYAML (pip install "activity-frames[yaml]"); without it the CLI falls back to JSON.

Run aframes <cmd> --help for the full flag set (--db, --include-text, --layout, ...).

Docs

AGENTS.mdIntegration guide for AI agents (install, MCP tools, replay loop, cautions)
SPEC.mdThe schema contract: measured vs inferred tiers
docs/mcp.mdMCP setup and the six tools in detail
docs/python.mdPython API reference
docs/cli.mdEvery command and flag
docs/troubleshooting.mdKeyed by the actual error you see
research/The cost instrument, measurements, and replay executor behind the paper
llms.txtCompressed repo map for LLM context windows

Status

v0.2. Developed and tested on macOS (Apple Silicon); an Intel macOS engine build is published but less exercised - reports welcome. No prebuilt Linux engine yet: on Linux, run your own recorder and point $AFRAMES_DB at its database (the compiler itself is tested on Linux in CI). Entity parsers cover LinkedIn, GitHub, GitLab, Google (Search/Docs/Gmail/Maps/Meet/Calendar), YouTube, X, Instagram, Reddit, Luma, Partiful, Product Hunt, Vercel, Supabase, Stripe (dashboard), Discord, Slack, Notion, Figma, Linear, Stack Overflow, Calendly, Crunchbase, Atlassian (Jira/Confluence), ChatGPT/Claude, localhost; unknown sites fall back to a generic page reference - always total, never lossy. Issues and parser PRs welcome.

Paper

Activity Frames: Deterministic Screen-Activity Compilation for Agent Memory and Replay
Nossa Iyamu, arXiv 2026

arXivHugging Face

@misc{iyamu2026activityframes,
title={Activity Frames: Deterministic Screen-Activity Compilation for Agent Memory and Replay},
author={Nossa Iyamu},
year={2026},
eprint={2608.05784},
archivePrefix={arXiv},
primaryClass={cs.AI},
url={https://arxiv.org/abs/2608.05784}
}

Built by Nossa Iyamu, maker of Nocta. MIT.

About

Turn your workday into structured workflows agents can execute. 100% local, served over MCP.

Topics

Resources

Contributing

Security policy

Stars

541 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

activity-frames powering Nocta

arXivHackerNoonHugging FacePyPIDownloadsDownloads/monthGitHub starsPythonMCPtestsLicense: MITWebsite

Turn your workday into structured workflows agents can execute.

Computer-use agents work every task out from scratch, even one you've done a hundred times. And between tasks, your agent has no idea what you've been doing all day, so it starts every conversation blind.

activity-frames fixes both. It records your screen locally and compiles what it sees into structured activity frames: bounded, deterministic records of the tasks you actually did. The recurring ones become workflows an agent can execute instead of working out again. So your repetitive computer tasks get done cheaper (running a compiled workflow costs almost no tokens) and more reliable (the same steps, grounded the same way every time, instead of guessing from a screenshot) - and everything else becomes context your agent can use.

pip install activity-frames
aframes record # start capturing (local, audio off by default)
aframes context # your last 2 hours, agent-ready

Want to go deeper?docs/mcp.md covers the six MCP tools, docs/python.md the Python API, docs/cli.md every flag, docs/troubleshooting.md the errors you might actually hit. AI agent integrating this? AGENTS.md is written for you.

What your agent sees

Capture stores instants: thousands of snapshot rows a day, each one saying "at 22:53:05, Chrome showed linkedin.com/in/...". Useless to reason over.

activity-frames compiles those instants into activity frames:

- id: f-0007app: Google Chromesite: linkedin.comstart: "20:24:04"end: "20:42:11"duration_min: 18.0pages:
- {kind: people_search, entity: "cto berlin", count: 2}
- {kind: profile, entity: john-doe}
- {kind: company, entity: acme-ai}input: {keys: 214, clicks: 31}evidence: {frame_ids: "99871..100147"}

And into a compact context block for any system prompt:

USER ACTIVITY (2026-08-10, local time; measured from screen capture, no interpretation):
coverage: 10:29-22:55, 529 active min, 15 apps
away: 13:02-14:39 (97m)
- 10:54-11:23 Google Chrome/app.cal.com (28.7m): dashboard:video x59
- 12:49-12:52 cmux (2.7m): ⠂ Build nocta-recorder ground-up screen recorder
- 12:52-13:02 Google Chrome/calendar.google.com (9.7m): calendar x20
- 20:53-20:56 Google Chrome/github.com (3.3m): user:bethvourc x5; repo:nossa-y/activity-frames x2
- 20:56-21:01 Google Chrome/hackernoon.com (5.2m): page:i-compiled-55-days-of-screen-activity-into-episodic-memory-for-my-ai-agent x12

That is a real day of mine (excerpt; the full block has 44 frames). It compiles to 1,371 tokens; the raw snapshot rows for the same day are 247,563. Compiles in under a second, costs zero tokens, no LLM in the loop.

Drop it into a prompt and your agent knows your day - ask "what was that article I had open around 9pm?" and it answers in one line, off the block alone:

aframes demo: compile a real day, ask the agent, get a one-line answer

Real exchange, MCP disabled - the answer comes from the compiled block only.

Workflows agents can execute

Computer-use agents re-derive every task from scratch - screenshot, reason, act, repeat - even for a workflow they've run a hundred times. That re-derivation is where the token cost goes, and it's waste: the workflow hasn't changed.

Because activity-frames compiles recurring activity deterministically, a task you've demonstrated becomes an executable script:

aframes steps --find "message john doe"
{
"steps": [
{"t": "20:24:09", "op": "focus", "target": "Google Chrome · LinkedIn", "n": 1},
{"t": "20:24:14", "op": "click", "target": "Search", "role": "TextField", "url": "https://www.linkedin.com/feed/", "n": 2},
{"t": "20:24:16", "op": "type", "chars": 8, "text": "john doe", "n": 3},
{"t": "20:24:21", "op": "click", "target": "John Doe", "role": "Link", "url": "https://www.linkedin.com/search/results/people/", "n": 4},
{"t": "20:24:29", "op": "click", "target": "Message", "role": "Button", "url": "https://www.linkedin.com/in/john-doe/", "n": 5},
{"t": "20:24:35", "op": "type", "chars": 71, "text": "hey, loved your post on agent memory - open to a quick chat next week?", "n": 6}
],
"step_count": 6,
"unresolved_clicks": 0
}

That's the replay view of a demonstrated run - ordered clicks grounded by element name and role, typed runs, focus changes. An agent repeats the task instead of re-deriving it: fill the slots with new values (a different name, the same steps) and execute. On the happy path it replays at zero model calls; anything unexpected halts and asks instead of guessing.

We measured how much agents overpay to re-derive workflows they've already performed - the Routine Overhead Ratio - on weeks of real activity, replicated it on a public web-task dataset, and built a deterministic executor that replays a compiled workflow in a real browser. Instrument, measurements, and executor: research/.

Passively-captured activity becomes deterministic action - and the cheapest computer task is the one an agent never reasons through twice.

Measured, not guessed

Agent memory today means conversation memory: what you told the model. What you actually did is the missing half - and the hard part is representing it without lying.

activity-frames enforces a two-tier contract (SPEC.md):

  • Tier 1, measured (this package): everything is derivable by deterministic code from capture data - sessions, durations, typed page entities, input volume, coverage gaps. No interpretation, no intent labels. Same input, same output, every time.
  • Tier 2, inferred (optional extension): tools that add interpretation must namespace it, tag confidence (high | medium | speculative), and link evidence. Facts and guesses can never silently mix.

Every frame carries evidence pointers back to raw capture rows. Every document declares its blind spots. What the system did not see, it says it did not see.

Use it from an agent (MCP)

# Claude Code
claude mcp add activity-frames -- aframes mcp

Any MCP client works: command aframes, args ["mcp"]. Six tools: get_context, get_activity, get_steps (expand one activity frame into its ordered click-by-click script - the replay view of a demonstrated run, so an agent can repeat the task instead of re-deriving it), get_day_summary, get_patterns (repetitive-workflow detection: repeated clicks, action sequences, URL patterns, app-switching loops, daily habits), and get_communications (email/messaging surfaces with the window titles seen on each — for many clients the title carries the subject or conversation name; a client that doesn't title its windows with the conversation leaves only its presence to report. Titles only, measured tier: message bodies are never read).

Use it from Python

fromactivity_framesimportActivityLoglog=ActivityLog()
doc=log.day() # today, structureddoc=log.recent(hours=2) # last 2 hoursprint(log.context(hours=2)) # paste-ready context block

Privacy model

  • Local only. Capture, storage, and compilation all happen on your machine. Nothing is uploaded anywhere, ever.
  • Read-only compilation. The compiler opens the capture database read-only.
  • Content opt-in at the output. Compiled documents carry input counts by default; typed-text content appears only if you explicitly pass --include-text (this also gates the repeated-text pattern detector). Be clear about the boundary: the capture database itself does store what the recorder sees, locally, so protect it like any sensitive file (FileVault, permissions).
  • Audio off by default.aframes record --audio to opt in.
  • No LLM in the compile path. Compilation is plain code, so no language model, local or remote, is involved in producing memory. The capture engine does run on-device OCR to read what is on screen; that stays on your machine.
  • You choose what leaves, when you paste a context block into an agent. Note that window titles and page entities originate from your screen and can contain third-party text; agents should treat them as data, not instructions.

Architecture

 capture engine compiler (this package) your agent
------------------ --------------------------- -----------------
screen snapshots --> sessionize (dwell, gaps, --> MCP tools /
accessibility tree flicker merge) context blocks /
input events entity typing (25+ sites) JSON, YAML, md
(local SQLite) enrichment, patterns

The default capture engine is nocta-recorder: aframes record provisions a pinned, MIT-licensed build, verifies its published sha256 before first run, and manages it for you (see ACKNOWLEDGMENTS.md); a nocta-recorder binary already on your PATH is used as-is (bring your own build). Already running your own recorder? Point $AFRAMES_DB at any capture database with compatible frames / ui_events / elements tables and skip aframes record entirely.

CLI

aframes record # start capture (--stop / --status / --audio)
aframes today # today's frames (YAML*)
aframes day 2026-07-03 -f json # any day, JSON
aframes context --hours 3 # agent context block
aframes apps # per-app time ledger
aframes patterns --days 7 # repetitive workflow detection
aframes comms --hours 24 # email/messaging surfaces + titles seen
aframes steps --frame f-0002 # one frame's click-by-click script (or --find "task query")
aframes mcp # MCP stdio server

*YAML output uses PyYAML (pip install "activity-frames[yaml]"); without it the CLI falls back to JSON.

Run aframes <cmd> --help for the full flag set (--db, --include-text, --layout, ...).

Docs

AGENTS.mdIntegration guide for AI agents (install, MCP tools, replay loop, cautions)
SPEC.mdThe schema contract: measured vs inferred tiers
docs/mcp.mdMCP setup and the six tools in detail
docs/python.mdPython API reference
docs/cli.mdEvery command and flag
docs/troubleshooting.mdKeyed by the actual error you see
research/The cost instrument, measurements, and replay executor behind the paper
llms.txtCompressed repo map for LLM context windows

Status

v0.2. Developed and tested on macOS (Apple Silicon); an Intel macOS engine build is published but less exercised - reports welcome. No prebuilt Linux engine yet: on Linux, run your own recorder and point $AFRAMES_DB at its database (the compiler itself is tested on Linux in CI). Entity parsers cover LinkedIn, GitHub, GitLab, Google (Search/Docs/Gmail/Maps/Meet/Calendar), YouTube, X, Instagram, Reddit, Luma, Partiful, Product Hunt, Vercel, Supabase, Stripe (dashboard), Discord, Slack, Notion, Figma, Linear, Stack Overflow, Calendly, Crunchbase, Atlassian (Jira/Confluence), ChatGPT/Claude, localhost; unknown sites fall back to a generic page reference - always total, never lossy. Issues and parser PRs welcome.

Paper

Activity Frames: Deterministic Screen-Activity Compilation for Agent Memory and Replay
Nossa Iyamu, arXiv 2026

arXivHugging Face

@misc{iyamu2026activityframes,
title={Activity Frames: Deterministic Screen-Activity Compilation for Agent Memory and Replay},
author={Nossa Iyamu},
year={2026},
eprint={2608.05784},
archivePrefix={arXiv},
primaryClass={cs.AI},
url={https://arxiv.org/abs/2608.05784}
}

Built by Nossa Iyamu, maker of Nocta. MIT.

About

Turn your workday into structured workflows agents can execute. 100% local, served over MCP.

Topics

Resources

Contributing

Security policy

Stars

541 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

activity-frames powering Nocta

arXivHackerNoonHugging FacePyPIDownloadsDownloads/monthGitHub starsPythonMCPtestsLicense: MITWebsite

Turn your workday into structured workflows agents can execute.

Computer-use agents work every task out from scratch, even one you've done a hundred times. And between tasks, your agent has no idea what you've been doing all day, so it starts every conversation blind.

activity-frames fixes both. It records your screen locally and compiles what it sees into structured activity frames: bounded, deterministic records of the tasks you actually did. The recurring ones become workflows an agent can execute instead of working out again. So your repetitive computer tasks get done cheaper (running a compiled workflow costs almost no tokens) and more reliable (the same steps, grounded the same way every time, instead of guessing from a screenshot) - and everything else becomes context your agent can use.

pip install activity-frames
aframes record # start capturing (local, audio off by default)
aframes context # your last 2 hours, agent-ready

Want to go deeper?docs/mcp.md covers the six MCP tools, docs/python.md the Python API, docs/cli.md every flag, docs/troubleshooting.md the errors you might actually hit. AI agent integrating this? AGENTS.md is written for you.

What your agent sees

Capture stores instants: thousands of snapshot rows a day, each one saying "at 22:53:05, Chrome showed linkedin.com/in/...". Useless to reason over.

activity-frames compiles those instants into activity frames:

- id: f-0007app: Google Chromesite: linkedin.comstart: "20:24:04"end: "20:42:11"duration_min: 18.0pages:
- {kind: people_search, entity: "cto berlin", count: 2}
- {kind: profile, entity: john-doe}
- {kind: company, entity: acme-ai}input: {keys: 214, clicks: 31}evidence: {frame_ids: "99871..100147"}

And into a compact context block for any system prompt:

USER ACTIVITY (2026-08-10, local time; measured from screen capture, no interpretation):
coverage: 10:29-22:55, 529 active min, 15 apps
away: 13:02-14:39 (97m)
- 10:54-11:23 Google Chrome/app.cal.com (28.7m): dashboard:video x59
- 12:49-12:52 cmux (2.7m): ⠂ Build nocta-recorder ground-up screen recorder
- 12:52-13:02 Google Chrome/calendar.google.com (9.7m): calendar x20
- 20:53-20:56 Google Chrome/github.com (3.3m): user:bethvourc x5; repo:nossa-y/activity-frames x2
- 20:56-21:01 Google Chrome/hackernoon.com (5.2m): page:i-compiled-55-days-of-screen-activity-into-episodic-memory-for-my-ai-agent x12

That is a real day of mine (excerpt; the full block has 44 frames). It compiles to 1,371 tokens; the raw snapshot rows for the same day are 247,563. Compiles in under a second, costs zero tokens, no LLM in the loop.

Drop it into a prompt and your agent knows your day - ask "what was that article I had open around 9pm?" and it answers in one line, off the block alone:

aframes demo: compile a real day, ask the agent, get a one-line answer

Real exchange, MCP disabled - the answer comes from the compiled block only.

Workflows agents can execute

Computer-use agents re-derive every task from scratch - screenshot, reason, act, repeat - even for a workflow they've run a hundred times. That re-derivation is where the token cost goes, and it's waste: the workflow hasn't changed.

Because activity-frames compiles recurring activity deterministically, a task you've demonstrated becomes an executable script:

aframes steps --find "message john doe"
{
"steps": [
{"t": "20:24:09", "op": "focus", "target": "Google Chrome · LinkedIn", "n": 1},
{"t": "20:24:14", "op": "click", "target": "Search", "role": "TextField", "url": "https://www.linkedin.com/feed/", "n": 2},
{"t": "20:24:16", "op": "type", "chars": 8, "text": "john doe", "n": 3},
{"t": "20:24:21", "op": "click", "target": "John Doe", "role": "Link", "url": "https://www.linkedin.com/search/results/people/", "n": 4},
{"t": "20:24:29", "op": "click", "target": "Message", "role": "Button", "url": "https://www.linkedin.com/in/john-doe/", "n": 5},
{"t": "20:24:35", "op": "type", "chars": 71, "text": "hey, loved your post on agent memory - open to a quick chat next week?", "n": 6}
],
"step_count": 6,
"unresolved_clicks": 0
}

That's the replay view of a demonstrated run - ordered clicks grounded by element name and role, typed runs, focus changes. An agent repeats the task instead of re-deriving it: fill the slots with new values (a different name, the same steps) and execute. On the happy path it replays at zero model calls; anything unexpected halts and asks instead of guessing.

We measured how much agents overpay to re-derive workflows they've already performed - the Routine Overhead Ratio - on weeks of real activity, replicated it on a public web-task dataset, and built a deterministic executor that replays a compiled workflow in a real browser. Instrument, measurements, and executor: research/.

Passively-captured activity becomes deterministic action - and the cheapest computer task is the one an agent never reasons through twice.

Measured, not guessed

Agent memory today means conversation memory: what you told the model. What you actually did is the missing half - and the hard part is representing it without lying.

activity-frames enforces a two-tier contract (SPEC.md):

  • Tier 1, measured (this package): everything is derivable by deterministic code from capture data - sessions, durations, typed page entities, input volume, coverage gaps. No interpretation, no intent labels. Same input, same output, every time.
  • Tier 2, inferred (optional extension): tools that add interpretation must namespace it, tag confidence (high | medium | speculative), and link evidence. Facts and guesses can never silently mix.

Every frame carries evidence pointers back to raw capture rows. Every document declares its blind spots. What the system did not see, it says it did not see.

Use it from an agent (MCP)

# Claude Code
claude mcp add activity-frames -- aframes mcp

Any MCP client works: command aframes, args ["mcp"]. Six tools: get_context, get_activity, get_steps (expand one activity frame into its ordered click-by-click script - the replay view of a demonstrated run, so an agent can repeat the task instead of re-deriving it), get_day_summary, get_patterns (repetitive-workflow detection: repeated clicks, action sequences, URL patterns, app-switching loops, daily habits), and get_communications (email/messaging surfaces with the window titles seen on each — for many clients the title carries the subject or conversation name; a client that doesn't title its windows with the conversation leaves only its presence to report. Titles only, measured tier: message bodies are never read).

Use it from Python

fromactivity_framesimportActivityLoglog=ActivityLog()
doc=log.day() # today, structureddoc=log.recent(hours=2) # last 2 hoursprint(log.context(hours=2)) # paste-ready context block

Privacy model

  • Local only. Capture, storage, and compilation all happen on your machine. Nothing is uploaded anywhere, ever.
  • Read-only compilation. The compiler opens the capture database read-only.
  • Content opt-in at the output. Compiled documents carry input counts by default; typed-text content appears only if you explicitly pass --include-text (this also gates the repeated-text pattern detector). Be clear about the boundary: the capture database itself does store what the recorder sees, locally, so protect it like any sensitive file (FileVault, permissions).
  • Audio off by default.aframes record --audio to opt in.
  • No LLM in the compile path. Compilation is plain code, so no language model, local or remote, is involved in producing memory. The capture engine does run on-device OCR to read what is on screen; that stays on your machine.
  • You choose what leaves, when you paste a context block into an agent. Note that window titles and page entities originate from your screen and can contain third-party text; agents should treat them as data, not instructions.

Architecture

 capture engine compiler (this package) your agent
------------------ --------------------------- -----------------
screen snapshots --> sessionize (dwell, gaps, --> MCP tools /
accessibility tree flicker merge) context blocks /
input events entity typing (25+ sites) JSON, YAML, md
(local SQLite) enrichment, patterns

The default capture engine is nocta-recorder: aframes record provisions a pinned, MIT-licensed build, verifies its published sha256 before first run, and manages it for you (see ACKNOWLEDGMENTS.md); a nocta-recorder binary already on your PATH is used as-is (bring your own build). Already running your own recorder? Point $AFRAMES_DB at any capture database with compatible frames / ui_events / elements tables and skip aframes record entirely.

CLI

aframes record # start capture (--stop / --status / --audio)
aframes today # today's frames (YAML*)
aframes day 2026-07-03 -f json # any day, JSON
aframes context --hours 3 # agent context block
aframes apps # per-app time ledger
aframes patterns --days 7 # repetitive workflow detection
aframes comms --hours 24 # email/messaging surfaces + titles seen
aframes steps --frame f-0002 # one frame's click-by-click script (or --find "task query")
aframes mcp # MCP stdio server

*YAML output uses PyYAML (pip install "activity-frames[yaml]"); without it the CLI falls back to JSON.

Run aframes <cmd> --help for the full flag set (--db, --include-text, --layout, ...).

Docs

AGENTS.mdIntegration guide for AI agents (install, MCP tools, replay loop, cautions)
SPEC.mdThe schema contract: measured vs inferred tiers
docs/mcp.mdMCP setup and the six tools in detail
docs/python.mdPython API reference
docs/cli.mdEvery command and flag
docs/troubleshooting.mdKeyed by the actual error you see
research/The cost instrument, measurements, and replay executor behind the paper
llms.txtCompressed repo map for LLM context windows

Status

v0.2. Developed and tested on macOS (Apple Silicon); an Intel macOS engine build is published but less exercised - reports welcome. No prebuilt Linux engine yet: on Linux, run your own recorder and point $AFRAMES_DB at its database (the compiler itself is tested on Linux in CI). Entity parsers cover LinkedIn, GitHub, GitLab, Google (Search/Docs/Gmail/Maps/Meet/Calendar), YouTube, X, Instagram, Reddit, Luma, Partiful, Product Hunt, Vercel, Supabase, Stripe (dashboard), Discord, Slack, Notion, Figma, Linear, Stack Overflow, Calendly, Crunchbase, Atlassian (Jira/Confluence), ChatGPT/Claude, localhost; unknown sites fall back to a generic page reference - always total, never lossy. Issues and parser PRs welcome.

Paper

Activity Frames: Deterministic Screen-Activity Compilation for Agent Memory and Replay
Nossa Iyamu, arXiv 2026

arXivHugging Face

@misc{iyamu2026activityframes,
title={Activity Frames: Deterministic Screen-Activity Compilation for Agent Memory and Replay},
author={Nossa Iyamu},
year={2026},
eprint={2608.05784},
archivePrefix={arXiv},
primaryClass={cs.AI},
url={https://arxiv.org/abs/2608.05784}
}

Built by Nossa Iyamu, maker of Nocta. MIT.

About

Turn your workday into structured workflows agents can execute. 100% local, served over MCP.

Topics

Resources

Contributing

Security policy

Stars

541 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

activity-frames powering Nocta

arXivHackerNoonHugging FacePyPIDownloadsDownloads/monthGitHub starsPythonMCPtestsLicense: MITWebsite

Turn your workday into structured workflows agents can execute.

Computer-use agents work every task out from scratch, even one you've done a hundred times. And between tasks, your agent has no idea what you've been doing all day, so it starts every conversation blind.

activity-frames fixes both. It records your screen locally and compiles what it sees into structured activity frames: bounded, deterministic records of the tasks you actually did. The recurring ones become workflows an agent can execute instead of working out again. So your repetitive computer tasks get done cheaper (running a compiled workflow costs almost no tokens) and more reliable (the same steps, grounded the same way every time, instead of guessing from a screenshot) - and everything else becomes context your agent can use.

pip install activity-frames
aframes record # start capturing (local, audio off by default)
aframes context # your last 2 hours, agent-ready

Want to go deeper?docs/mcp.md covers the six MCP tools, docs/python.md the Python API, docs/cli.md every flag, docs/troubleshooting.md the errors you might actually hit. AI agent integrating this? AGENTS.md is written for you.

What your agent sees

Capture stores instants: thousands of snapshot rows a day, each one saying "at 22:53:05, Chrome showed linkedin.com/in/...". Useless to reason over.

activity-frames compiles those instants into activity frames:

- id: f-0007app: Google Chromesite: linkedin.comstart: "20:24:04"end: "20:42:11"duration_min: 18.0pages:
- {kind: people_search, entity: "cto berlin", count: 2}
- {kind: profile, entity: john-doe}
- {kind: company, entity: acme-ai}input: {keys: 214, clicks: 31}evidence: {frame_ids: "99871..100147"}

And into a compact context block for any system prompt:

USER ACTIVITY (2026-08-10, local time; measured from screen capture, no interpretation):
coverage: 10:29-22:55, 529 active min, 15 apps
away: 13:02-14:39 (97m)
- 10:54-11:23 Google Chrome/app.cal.com (28.7m): dashboard:video x59
- 12:49-12:52 cmux (2.7m): ⠂ Build nocta-recorder ground-up screen recorder
- 12:52-13:02 Google Chrome/calendar.google.com (9.7m): calendar x20
- 20:53-20:56 Google Chrome/github.com (3.3m): user:bethvourc x5; repo:nossa-y/activity-frames x2
- 20:56-21:01 Google Chrome/hackernoon.com (5.2m): page:i-compiled-55-days-of-screen-activity-into-episodic-memory-for-my-ai-agent x12

That is a real day of mine (excerpt; the full block has 44 frames). It compiles to 1,371 tokens; the raw snapshot rows for the same day are 247,563. Compiles in under a second, costs zero tokens, no LLM in the loop.

Drop it into a prompt and your agent knows your day - ask "what was that article I had open around 9pm?" and it answers in one line, off the block alone:

aframes demo: compile a real day, ask the agent, get a one-line answer

Real exchange, MCP disabled - the answer comes from the compiled block only.

Workflows agents can execute

Computer-use agents re-derive every task from scratch - screenshot, reason, act, repeat - even for a workflow they've run a hundred times. That re-derivation is where the token cost goes, and it's waste: the workflow hasn't changed.

Because activity-frames compiles recurring activity deterministically, a task you've demonstrated becomes an executable script:

aframes steps --find "message john doe"
{
"steps": [
{"t": "20:24:09", "op": "focus", "target": "Google Chrome · LinkedIn", "n": 1},
{"t": "20:24:14", "op": "click", "target": "Search", "role": "TextField", "url": "https://www.linkedin.com/feed/", "n": 2},
{"t": "20:24:16", "op": "type", "chars": 8, "text": "john doe", "n": 3},
{"t": "20:24:21", "op": "click", "target": "John Doe", "role": "Link", "url": "https://www.linkedin.com/search/results/people/", "n": 4},
{"t": "20:24:29", "op": "click", "target": "Message", "role": "Button", "url": "https://www.linkedin.com/in/john-doe/", "n": 5},
{"t": "20:24:35", "op": "type", "chars": 71, "text": "hey, loved your post on agent memory - open to a quick chat next week?", "n": 6}
],
"step_count": 6,
"unresolved_clicks": 0
}

That's the replay view of a demonstrated run - ordered clicks grounded by element name and role, typed runs, focus changes. An agent repeats the task instead of re-deriving it: fill the slots with new values (a different name, the same steps) and execute. On the happy path it replays at zero model calls; anything unexpected halts and asks instead of guessing.

We measured how much agents overpay to re-derive workflows they've already performed - the Routine Overhead Ratio - on weeks of real activity, replicated it on a public web-task dataset, and built a deterministic executor that replays a compiled workflow in a real browser. Instrument, measurements, and executor: research/.

Passively-captured activity becomes deterministic action - and the cheapest computer task is the one an agent never reasons through twice.

Measured, not guessed

Agent memory today means conversation memory: what you told the model. What you actually did is the missing half - and the hard part is representing it without lying.

activity-frames enforces a two-tier contract (SPEC.md):

  • Tier 1, measured (this package): everything is derivable by deterministic code from capture data - sessions, durations, typed page entities, input volume, coverage gaps. No interpretation, no intent labels. Same input, same output, every time.
  • Tier 2, inferred (optional extension): tools that add interpretation must namespace it, tag confidence (high | medium | speculative), and link evidence. Facts and guesses can never silently mix.

Every frame carries evidence pointers back to raw capture rows. Every document declares its blind spots. What the system did not see, it says it did not see.

Use it from an agent (MCP)

# Claude Code
claude mcp add activity-frames -- aframes mcp

Any MCP client works: command aframes, args ["mcp"]. Six tools: get_context, get_activity, get_steps (expand one activity frame into its ordered click-by-click script - the replay view of a demonstrated run, so an agent can repeat the task instead of re-deriving it), get_day_summary, get_patterns (repetitive-workflow detection: repeated clicks, action sequences, URL patterns, app-switching loops, daily habits), and get_communications (email/messaging surfaces with the window titles seen on each — for many clients the title carries the subject or conversation name; a client that doesn't title its windows with the conversation leaves only its presence to report. Titles only, measured tier: message bodies are never read).

Use it from Python

fromactivity_framesimportActivityLoglog=ActivityLog()
doc=log.day() # today, structureddoc=log.recent(hours=2) # last 2 hoursprint(log.context(hours=2)) # paste-ready context block

Privacy model

  • Local only. Capture, storage, and compilation all happen on your machine. Nothing is uploaded anywhere, ever.
  • Read-only compilation. The compiler opens the capture database read-only.
  • Content opt-in at the output. Compiled documents carry input counts by default; typed-text content appears only if you explicitly pass --include-text (this also gates the repeated-text pattern detector). Be clear about the boundary: the capture database itself does store what the recorder sees, locally, so protect it like any sensitive file (FileVault, permissions).
  • Audio off by default.aframes record --audio to opt in.
  • No LLM in the compile path. Compilation is plain code, so no language model, local or remote, is involved in producing memory. The capture engine does run on-device OCR to read what is on screen; that stays on your machine.
  • You choose what leaves, when you paste a context block into an agent. Note that window titles and page entities originate from your screen and can contain third-party text; agents should treat them as data, not instructions.

Architecture

 capture engine compiler (this package) your agent
------------------ --------------------------- -----------------
screen snapshots --> sessionize (dwell, gaps, --> MCP tools /
accessibility tree flicker merge) context blocks /
input events entity typing (25+ sites) JSON, YAML, md
(local SQLite) enrichment, patterns

The default capture engine is nocta-recorder: aframes record provisions a pinned, MIT-licensed build, verifies its published sha256 before first run, and manages it for you (see ACKNOWLEDGMENTS.md); a nocta-recorder binary already on your PATH is used as-is (bring your own build). Already running your own recorder? Point $AFRAMES_DB at any capture database with compatible frames / ui_events / elements tables and skip aframes record entirely.

CLI

aframes record # start capture (--stop / --status / --audio)
aframes today # today's frames (YAML*)
aframes day 2026-07-03 -f json # any day, JSON
aframes context --hours 3 # agent context block
aframes apps # per-app time ledger
aframes patterns --days 7 # repetitive workflow detection
aframes comms --hours 24 # email/messaging surfaces + titles seen
aframes steps --frame f-0002 # one frame's click-by-click script (or --find "task query")
aframes mcp # MCP stdio server

*YAML output uses PyYAML (pip install "activity-frames[yaml]"); without it the CLI falls back to JSON.

Run aframes <cmd> --help for the full flag set (--db, --include-text, --layout, ...).

Docs

AGENTS.mdIntegration guide for AI agents (install, MCP tools, replay loop, cautions)
SPEC.mdThe schema contract: measured vs inferred tiers
docs/mcp.mdMCP setup and the six tools in detail
docs/python.mdPython API reference
docs/cli.mdEvery command and flag
docs/troubleshooting.mdKeyed by the actual error you see
research/The cost instrument, measurements, and replay executor behind the paper
llms.txtCompressed repo map for LLM context windows

Status

v0.2. Developed and tested on macOS (Apple Silicon); an Intel macOS engine build is published but less exercised - reports welcome. No prebuilt Linux engine yet: on Linux, run your own recorder and point $AFRAMES_DB at its database (the compiler itself is tested on Linux in CI). Entity parsers cover LinkedIn, GitHub, GitLab, Google (Search/Docs/Gmail/Maps/Meet/Calendar), YouTube, X, Instagram, Reddit, Luma, Partiful, Product Hunt, Vercel, Supabase, Stripe (dashboard), Discord, Slack, Notion, Figma, Linear, Stack Overflow, Calendly, Crunchbase, Atlassian (Jira/Confluence), ChatGPT/Claude, localhost; unknown sites fall back to a generic page reference - always total, never lossy. Issues and parser PRs welcome.

Paper

Activity Frames: Deterministic Screen-Activity Compilation for Agent Memory and Replay
Nossa Iyamu, arXiv 2026

arXivHugging Face

@misc{iyamu2026activityframes,
title={Activity Frames: Deterministic Screen-Activity Compilation for Agent Memory and Replay},
author={Nossa Iyamu},
year={2026},
eprint={2608.05784},
archivePrefix={arXiv},
primaryClass={cs.AI},
url={https://arxiv.org/abs/2608.05784}
}

Built by Nossa Iyamu, maker of Nocta. MIT.

About

Turn your workday into structured workflows agents can execute. 100% local, served over MCP.

Topics

Resources

Contributing

Security policy

Stars

541 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages