{"slug": "skills-over-system-prompts-building-an-anki-tutor-with-the-antigravity-sdk", "title": "Skills over System Prompts: Building an Anki Tutor with the Antigravity SDK", "summary": "A developer built an AI-powered Anki tutor using Google's Antigravity SDK, replacing a monolithic system prompt with reusable skills and lifecycle hooks. The agent system integrates with Anki via AnkiConnect, supports voice input, and runs across terminal and Telegram surfaces. The project demonstrates how the Antigravity SDK enables composing agent runtime features like custom tools, skills, and safety policies into a real-world study workflow.", "body_md": "AI has made me a little lazier.\n\nNot dramatically lazy. Not \"the robots will do everything\" lazy. More like: once you get used to asking an agent to do boring work, every small manual workflow starts looking suspicious.\n\nAnki is a perfect example.\n\nAnki is great. I use it to remember things I study, subjects I work on, and the weird little decisions hidden inside codebases. Spaced repetition works. The problem is not Anki.\n\nThe problem is me.\n\nI can already see the rot setting in. On complex cards, my brain starts negotiating with itself. \"Yeah, I basically knew that.\" \"Close enough.\" \"I would have remembered it in context.\" Then I press Good and move on.\n\nThat is not studying. That is self-certified vibes.\n\nWhat I actually wanted was a study buddy sitting on top of my real Anki collection. Someone to ask the card, wait for my answer, reveal the real answer, compare it honestly, explain the gap, and only then help decide whether it was Again, Hard, Good, or Easy.\n\nAI is annoyingly good for that.\n\nIt is also useful when taking over a new project. When I enter a repo, I do not only want a summary. I want to be quizzed later on the key decisions, the architecture, the gotchas, and the \"why is it like this?\" parts. Anki is great for that too.\n\nBut I am still lazy.\n\nI am not going to manually write every card. I am not going to keep every deck updated by hand. And if I am studying from my phone, I am definitely not going to type long answers into a chat just so the agent can grade me. Voice needs to work too.\n\nSo the project quickly stopped being \"connect Gemini to Anki.\"\n\nIt became a small agent system:\n\nThat is a lot of behavior.\n\nMy first instinct was the usual one: write a bigger system prompt. Tell the agent how to run a study session. Tell it how to write good flashcards. Tell it how to inspect a codebase and turn architecture into cards. Tell it how to behave differently in Telegram. Tell it not to touch scheduling unless I approve.\n\nThat works for about ten minutes.\n\nThen the system prompt becomes a junk drawer.\n\nThe hard part was not giving the agent tools.\n\nThe hard part was giving it habits.\n\nThat is where the Google Antigravity SDK fit really well. It gives you the agent runtime as a Python library: custom tools, reusable skills, lifecycle hooks, safety policies, streaming, triggers, and multiple ways to run the same agent logic from different surfaces.\n\nThe Antigravity SDK is not just a wrapper around a chat model.\n\nIt gives you programmatic access to the same agent runtime behind Google Antigravity 2.0 and the Antigravity CLI, but from Python.\n\nThat matters because a real agent is not only a model call. A real agent needs:\n\nThe SDK puts those behind one main abstraction: `Agent`\n\n.\n\nThe smallest useful version really is tiny:\n\n``` python\nimport asyncio\nfrom google.antigravity import Agent, LocalAgentConfig\n\nasync def main():\n    config = LocalAgentConfig()\n    async with Agent(config) as agent:\n        response = await agent.chat(\"What files are in the current directory?\")\n        print(await response.text())\n\nif __name__ == \"__main__\":\n    asyncio.run(main())\n```\n\nInstall it with:\n\n```\npip install google-antigravity\n```\n\nThen set a Gemini API key from Google AI Studio:\n\n```\nexport GEMINI_API_KEY=\"your-key-here\"\n```\n\nThat is the hello world.\n\nThe useful version starts when you compose the runtime features around a real workflow.\n\nIn this project, the Antigravity SDK pieces mapped like this:\n\n| Antigravity SDK capability | Where I used it |\n|---|---|\n`Agent` / `LocalAgentConfig`\n|\nthe terminal tutor, Telegram tutor, and deck builder all run on the same agent runtime |\n| Custom Python tools | AnkiConnect actions like `get_due_cards` , `show_answer` , `rate_card` , and `add_notes`\n|\n`skills_paths` |\nshared `review-buddy` , `plain-cards` , and `codebase-cards` behavior packages |\n| Lifecycle hooks | sync on session start/end, deck backup before writes, audit log after scheduling changes, tool-error recovery |\n| Safety policies | practice mode blocks `rate_card` so cram sessions cannot change real scheduling |\n| Streaming | the deck builder prints progress while the agent researches and creates cards |\n| Triggers | watch mode reacts to `.py` file changes and asks the agent to card important changes |\n| Built-in read-only tools | codebase mode lets the agent inspect a repo without editing it |\n\nThat list is the reason this worked better as an SDK project than as one giant prompt around a model call.\n\nNow, the first useful step: give the agent hands.\n\nAnki already has an HTTP API through the AnkiConnect add-on. The entire bridge is basically one POST to localhost:\n\n``` python\ndef invoke(action: str, **params):\n    response = requests.post(\n        \"http://localhost:8765\",\n        json={\"action\": action, \"version\": 6, \"params\": params},\n        timeout=30,\n    )\n    response.raise_for_status()\n    payload = response.json()\n    if payload[\"error\"]:\n        raise RuntimeError(payload[\"error\"])\n    return payload[\"result\"]\n```\n\nFrom there, the agent tools are just normal Python functions.\n\nA simplified version:\n\n``` php\ndef list_decks() -> str:\n    \"\"\"List all Anki decks with their due counts.\"\"\"\n    decks = invoke(\"deckNames\")\n    stats = invoke(\"getDeckStats\", decks=decks)\n    return json.dumps(stats)\n\ndef get_due_cards(deck: str = \"\", limit: int = 5) -> str:\n    \"\"\"Return due cards without revealing the answer side.\"\"\"\n    query = f'deck:\"{deck}\" is:due' if deck else \"is:due\"\n    card_ids = invoke(\"findCards\", query=query)[:limit]\n    cards = invoke(\"cardsInfo\", cards=card_ids)\n    return json.dumps(cards)\n\ndef rate_card(card_id: int, rating: int) -> str:\n    \"\"\"Submit a user-confirmed Anki rating: 1 Again, 2 Hard, 3 Good, 4 Easy.\"\"\"\n    invoke(\"answerCards\", answers=[{\"cardId\": card_id, \"ease\": rating}])\n    return json.dumps({\"rated\": card_id, \"rating\": rating})\n```\n\nThen register them with the SDK:\n\n``` python\nfrom google.antigravity import LocalAgentConfig\n\nconfig = LocalAgentConfig(\n    tools=[list_decks, get_due_cards, rate_card],\n)\n```\n\nThat is one of the nicest parts of the SDK: custom tools do not require a separate server. For this version, I did not need MCP, a framework, a schema generator, or a second process.\n\nThe agent can call plain Python.\n\nIn the real project I ended up with more tools:\n\n`list_decks`\n\n`get_due_cards`\n\n`show_answer`\n\n`rate_card`\n\n`find_notes`\n\n`add_note`\n\n`add_notes`\n\n`update_note`\n\n`suspend_card`\n\n`unsuspend_card`\n\n`undo`\n\n`get_stats`\n\n`sync`\n\nThat was enough to make the tutor useful.\n\nThis is the first pattern:\n\nPut capabilities in tools.\n\nTools are the agent's hands. But hands are not behavior.\n\nFor behavior, I used skills.\n\nAt first, I tried to describe everything in the agent's system instructions.\n\nThe tutor needs to know how to run a review session:\n\nIt also needs to know how to write good cards:\n\nThen the deck builder needs another workflow:\n\nThen the codebase deck builder needs a different workflow:\n\nThen Telegram needs shorter replies because nobody wants a wall of Markdown on a phone.\n\nYou can put all of that into one system prompt.\n\nBut you should not.\n\nA giant system prompt has three problems:\n\nThis is exactly the problem skills solve.\n\nThe shape changed from this:\n\n```\nsystem prompt = tutor rules\n              + card-writing rules\n              + codebase-exploration rules\n              + Telegram style rules\n              + safety reminders\n              + whatever I forgot last week\n```\n\nInto this:\n\n```\nsystem prompt  = identity + hard safety floor\nreview-buddy   = study-session behavior\nplain-cards    = card-writing behavior\ncodebase-cards = repo-exploration behavior\nhooks/policies = enforcement and receipts\n```\n\nThat is the real pattern behind the title.\n\nNot \"make the prompt better.\"\n\nMake the prompt smaller.\n\nA skill is a folder with a `SKILL.md`\n\nfile inside it.\n\nMy project has three:\n\n```\n.agents/skills/\n  plain-cards/\n    SKILL.md\n  review-buddy/\n    SKILL.md\n  codebase-cards/\n    SKILL.md\n```\n\nEach skill starts with a tiny bit of frontmatter.\n\nFor example, the review skill begins like this:\n\n```\n---\nname: review-buddy\ndescription: Playbook for running an interactive Anki review session — quiz one card at a time, grade recall together, submit ratings, repair noisy or broken cards.\n---\n```\n\nThat `description`\n\nis not just documentation for humans. It is the lightweight discovery layer. The agent can see what skills exist, then load the full instructions only when the task calls for them.\n\nA skill is not a service. It is not an MCP server. It is not a deployment. It is a behavior package sitting on disk, ready to be pulled into the agent when needed.\n\nThen the SDK loads the skill directory:\n\n```\nconfig = LocalAgentConfig(\n    system_instructions=SYSTEM_INSTRUCTIONS,\n    tools=ALL_TOOLS,\n    skills_paths=[\".agents/skills\"],\n)\n```\n\nThe key idea is simple:\n\nThe system prompt says who the agent is. Skills say what job it is currently doing.\n\nFor this project, the system prompt stays small. It says the agent is a friendly flashcard tutor working with a real Anki collection.\n\nThe details live in skills.\n\n`review-buddy`\n\n: the study session playbook\nThis skill describes how to run a review session.\n\nIt covers the rhythm:\n\nThis is not code. It is behavioral protocol.\n\nThat distinction matters. The review flow is not tied to terminal I/O, Telegram messages, or AnkiConnect. It is just the way a good tutor should behave.\n\n`plain-cards`\n\n: the card-writing style guide\nThis skill handles card quality.\n\nIt tells the agent to write cards that are:\n\nA bad flashcard is worse than no flashcard. It creates fake progress. The model can generate ten cards in seconds, but without a style guide it will happily generate ten vague cards that future me will hate.\n\nSo card writing became a skill.\n\n`codebase-cards`\n\n: the repo exploration protocol\nThis one is for turning source code into Anki cards.\n\nThe agent is told to inspect the repo breadth-first, identify architecture, data flow, responsibilities, and gotchas, then turn only the useful findings into cards.\n\nThat skill powers code mode in the deck builder:\n\n```\npython deck_builder.py \"overall architecture\" --path ~/my/project --count 6\n```\n\nThe focus hint changes, but the exploration protocol stays the same.\n\nThis is the second pattern:\n\nPut reusable behavior in skills.\n\nNot in the system prompt. Not duplicated across entrypoints. Not buried in Python conditionals.\n\nA skill is just a file, but it changes the shape of the whole project.\n\nOnce the behavior lived in skills, adding new surfaces became much easier.\n\nThe architecture looked like this:\n\n```\n                         .agents/skills/\n                  ┌──────────┼──────────┐\n                  │          │          │\n           review-buddy  plain-cards  codebase-cards\n                  │          │          │\n                  └──────────┼──────────┘\n                             │\n                    LocalAgentConfig\n                             │\n       ┌─────────────────────┼─────────────────────┐\n       │                     │                     │\n  terminal tutor        Telegram tutor        deck builder\n    tutor.py          telegram_tutor.py      deck_builder.py\n```\n\nThe terminal tutor is the simplest surface:\n\n```\nasync with Agent(config) as agent:\n    await run_interactive_loop(agent)\n```\n\nThe Telegram tutor uses the same agent differently:\n\n``` php\nasync def chat_response(agent: Agent, prompt: str) -> str:\n    response = await agent.chat(prompt)\n    return \"\".join([token async for token in response])\n```\n\nThe deck builder streams output as it works:\n\n```\nresponse = await agent.chat(message)\nasync for token in response:\n    print(token, end=\"\", flush=True)\n```\n\nDifferent surfaces. Same runtime. Same skills.\n\nThat is the part I liked most. Telegram did not need a copied review prompt. The deck builder did not need its own card-writing manifesto. The codebase mode did not need a separate app-specific doctrine.\n\nThey all loaded the same skill directory.\n\nThe terminal version is the baseline.\n\nStart Anki, run the tutor, and ask naturally:\n\n```\npython tutor.py\n```\n\nThen:\n\n```\nquiz me on XYZ\n```\n\nThe tutor lists due cards, asks one question, waits for my answer, reveals the real Anki answer, compares, teaches, and suggests a rating.\n\nThe important part: it does not update scheduling just because the model thinks I got the answer right.\n\nThe review loop is human-in-the-loop by design:\n\n```\nAgent: I would rate this Good (3). You had the main idea but missed the date.\nUser: yes\nAgent: rated 3. Next card...\n```\n\nOr I can override it:\n\n```\nAgent: I would rate this Hard (2).\nUser: actually 1\nAgent: rated Again (1). Let's reinforce it.\n```\n\nSpaced repetition is stateful. A bad rating affects the future schedule. So the model can suggest, but I decide.\n\nThat is not just a prompt preference. It is the product boundary.\n\nThe second surface was Telegram.\n\nNot because Telegram is fancy. Because the best study app is the one I actually open.\n\nThe Telegram bot long-polls the Bot API, sends messages into the same Antigravity agent, and returns the response. It also supports voice notes: speak the answer, transcribe it, and feed the transcript back into the tutor as text.\n\nThe agent gets a small extra instruction:\n\n```\nTELEGRAM_INSTRUCTIONS = \"\"\"\nYou are chatting through Telegram on a phone. Keep replies short and plain\ntext only — no markdown headers, tables, or code fences. One card per message.\n\"\"\"\n```\n\nEverything else stays shared.\n\nSame Anki tools. Same hooks. Same skills.\n\nI also added due-card nudges without spending model tokens. Every 30 minutes, plain Python checks Anki deck counts. If cards are waiting, the bot sends a short reminder:\n\n```\n25 cards waiting (X 5, Y 8). Say 'quiz me' to start.\n```\n\nNo LLM needed. No reasoning needed. Just deterministic code.\n\nThis became a useful design rule:\n\nDo not use the model for work a\n\n`for`\n\nloop can do.\n\nThe agent is for tutoring. The nudge is just a counter.\n\nThe third surface is a deck builder.\n\nIt has two modes.\n\nWeb mode:\n\n```\npython deck_builder.py \"Ottoman Empire\" --deck \"History\" --count 8\n```\n\nCodebase mode:\n\n```\npython deck_builder.py \"error handling and edge cases\" --path ~/my/project --count 6\n```\n\nWeb mode gives the agent a small research toolset: Wikipedia search, Wikipedia read, and URL fetch. Then it asks the agent to create cards using the `plain-cards`\n\nskill.\n\nCodebase mode is more interesting. The SDK can give the agent built-in file tools scoped to a workspace. I enabled read-only access:\n\n``` python\nfrom google.antigravity.types import BuiltinTools, CapabilitiesConfig\n\nconfig = LocalAgentConfig(\n    tools=[add_notes, list_decks],\n    workspaces=[code_path],\n    capabilities=CapabilitiesConfig(\n        enabled_tools=BuiltinTools.read_only()\n    ),\n    skills_paths=[\".agents/skills\"],\n)\n```\n\nThat means the agent can inspect the target repo, but not edit it.\n\nFor a deck builder, that is the right permission boundary. It needs to read code and create Anki notes. It does not need to modify the project.\n\nThis is where `codebase-cards`\n\nactivates. The agent explores the repo, identifies the concepts worth remembering, then writes cards through `add_notes`\n\n.\n\nAt the end, I do not trust the model's narration. The script queries Anki to verify the cards exist.\n\n``` php\ndef cards_in_anki(deck: str) -> int:\n    result = json.loads(find_notes(f'deck:\"{deck}\" tag:auto-researched', 100))\n    return len(result) if isinstance(result, list) else 0\n```\n\nIf the model says it created cards but Anki has zero, the script nudges it to try again.\n\nThat became another rule:\n\nTrust the system receipt, not the model narration.\n\nThe SDK also supports triggers: background tasks that react to external events and push messages into the agent.\n\nI used a file-change trigger for codebase card generation.\n\nThe idea: while I work on a project, if a Python file changes, the agent can inspect the change and decide whether it introduced something worth remembering.\n\nSimplified:\n\n``` python\nfrom google.antigravity.triggers import on_file_change\n\ndef make_watch_trigger(path, deck, tag):\n    async def on_change(ctx, changes):\n        paths = sorted({c.path for c in changes if c.path.endswith(\".py\")})\n        if not paths:\n            return\n\n        await ctx.send(\n            f\"These files changed: {', '.join(paths)}. \"\n            f\"Create cards in deck {deck} if the change is worth remembering.\"\n        )\n\n    return on_file_change(path, on_change)\n```\n\nRun it like this:\n\n```\npython deck_builder.py \"as I work\" --path ~/my/project --watch\n```\n\nThis is where the project started feeling less like a chatbot and more like a sidecar.\n\nI edit code. The trigger wakes the agent. The codebase skill tells it how to inspect the change. The card-writing skill tells it how to write good cards. The Anki tool creates the notes.\n\nNo new server. No custom scheduler. No giant prompt.\n\nJust SDK triggers plus skills.\n\nSkills are guidance.\n\nPolicies and hooks are enforcement.\n\nThat line is the difference between a fun demo and a tool I can leave connected to my real Anki collection.\n\nThe Antigravity SDK has declarative safety policies and lifecycle hooks. I used both.\n\nSometimes I want to cram without touching Anki scheduling.\n\nA prompt instruction is not enough for that. If the agent forgets and calls `rate_card`\n\n, the schedule changes.\n\nSo practice mode denies the tool at the harness level:\n\n``` python\nfrom google.antigravity.hooks import policy\n\npolicies = policy.confirm_run_command()\n\nif practice_mode:\n    policies = policies + [\n        policy.deny(\"rate_card\", name=\"practice_mode\")\n    ]\n```\n\nNow `rate_card`\n\nis blocked even if the model tries to call it.\n\nThat is the kind of safety I want: not vibes, not trust, not \"please don't\". A runtime boundary.\n\nThe SDK hook system lets you observe or intervene at lifecycle points.\n\nI used session hooks to sync Anki:\n\n``` python\n@hooks.on_session_start\nasync def sync_on_start():\n    sync_anki()\n\n@hooks.on_session_end\nasync def sync_on_end():\n    sync_anki()\n```\n\nI used a pre-tool-call Decide hook to back up a deck before note writes:\n\n``` python\n@hooks.pre_tool_call_decide\nasync def backup_before_note_writes(tool_call):\n    if tool_call.name in (\"add_note\", \"add_notes\"):\n        backup_deck(tool_call.args[\"deck\"])\n    return hooks.HookResult(allow=True)\n```\n\nI used a post-tool-call Inspect hook to audit scheduling changes:\n\n``` python\n@hooks.post_tool_call\nasync def audit_scheduling_changes(result):\n    if result.name in {\"rate_card\", \"undo\", \"suspend_card\", \"unsuspend_card\"}:\n        append_jsonl(\"backups/scheduling_audit.jsonl\", result)\n```\n\nAnd I used a Transform hook to turn ugly tool errors into recovery hints the model can act on:\n\n``` python\n@hooks.on_tool_error\nasync def recover_from_tool_error(error):\n    if isinstance(error, requests.Timeout):\n        return \"AnkiConnect timed out. Ask the user to check Anki, then retry.\"\n    return None\n```\n\nThis is one of the strongest parts of the SDK.\n\nThe model does not need to remember to audit itself. The harness does it.\n\nThe model does not need to remember to back up a deck before writing. The hook does it.\n\nThe model does not get to bypass practice mode. The policy blocks it.\n\nThe pattern became clear:\n\nThat separation is the architecture.\n\nA few things worked better than expected.\n\nI originally thought I might need to build an MCP server immediately.\n\nI did not.\n\nFor one application, custom Python functions were simpler. The SDK already knows how to expose them as tools. That kept the first version small.\n\nMCP is still useful when you want the same tools available across multiple clients. But for an SDK-native app, Python functions are the shortest path.\n\nThis was the biggest win.\n\nThe base system instructions stayed focused. The detailed workflows moved into skills.\n\nWhen I improved card-writing rules, terminal, Telegram, and deck builder all benefited. I did not need to update three prompts.\n\nAnki is not a toy database. It is my real spaced-repetition schedule.\n\nThe hooks gave me a deterministic layer around model behavior:\n\nThat made the agent feel much less like a random chatbot with database access.\n\nThe file watcher was small, but it changed the mental model.\n\nThe agent was no longer only something I talked to. It could react to work happening around it.\n\nThat is where SDK agents get interesting: not just chat, but event-driven labor.\n\nA few caveats.\n\nSkills are instructions. They improve behavior, but they are still model-read guidance.\n\nIf something must be impossible, use a policy or remove the tool.\n\nThat is why practice mode denies `rate_card`\n\ninstead of merely asking the model not to call it.\n\nAnkiConnect is simple, but it has quirks.\n\nFor example, `answerCards`\n\ncan return success even for bad card IDs unless you pre-check the card. Some note updates silently fail if the note is open in Anki's browser window. AnkiConnect also runs inside Anki's Qt process, so you should not treat it like a high-concurrency API.\n\nThe fix is boring and important: validate inside tools.\n\nThe Telegram bot supports voice answers, but I kept transcription outside the agent loop. A direct Gemini transcription call turns the voice note into text, then the transcript goes into the tutor.\n\nThat was simpler and more reliable for this build.\n\nThe lesson: use the SDK where it makes the architecture cleaner. Do not force every feature through the agent if a direct call is simpler.\n\nIf you want to build your own version of this pattern, I would do it in this order.\n\nDo not start with a platform.\n\nPick one annoying workflow with real state behind it:\n\nThe state matters. Agents get interesting when they can act on something real.\n\nKeep the tools boring.\n\n``` php\ndef search_items(query: str) -> str:\n    \"\"\"Search the user's records.\"\"\"\n    ...\n\ndef create_item(title: str, body: str) -> str:\n    \"\"\"Create a new record after user approval.\"\"\"\n    ...\n```\n\nRegister them:\n\n```\nconfig = LocalAgentConfig(\n    tools=[search_items, create_item],\n)\n```\n\nMake tools validate inputs. Do not rely on the model to pass perfect IDs.\n\nCreate a skill folder:\n\n```\n.agents/skills/my-workflow/SKILL.md\n```\n\nA minimal skill:\n\n```\n---\nname: my-workflow\ndescription: Use when helping the user process and update records in this system.\n---\n\n# My Workflow\n\n1. Inspect the current record before changing it.\n2. Propose the change in plain language.\n3. Wait for user confirmation before writing.\n4. After writing, verify the record exists.\n```\n\nThen load it:\n\n```\nconfig = LocalAgentConfig(\n    tools=TOOLS,\n    skills_paths=[\".agents/skills\"],\n)\n```\n\nThis is the move: do not keep growing the system prompt forever.\n\nIf a tool should never run in a mode, deny it.\n\n```\npolicies = [\n    policy.deny(\"delete_record\", name=\"no_deletes\"),\n]\n```\n\nIf shell execution should require confirmation, keep the default guard:\n\n```\npolicies = policy.confirm_run_command()\n```\n\nThe model can misunderstand a skill. It cannot ignore a denied tool.\n\nUse hooks for things that should happen regardless of whether the model remembers them:\n\n``` python\n@hooks.post_tool_call\nasync def audit(result):\n    write_log({\n        \"tool\": result.name,\n        \"result\": result.result,\n        \"error\": result.error,\n    })\n```\n\nOnce the behavior lives in tools and skills, a second surface becomes much cheaper.\n\nTerminal first. Then Telegram, Slack, web, cron, or file triggers.\n\nThe surface should be thin. The agent behavior should not live there.\n\nThe old way to build an AI feature was to write a large prompt and hope the model followed it.\n\nThat is not enough for real agents.\n\nA real agent needs separation of concerns:\n\n```\nCapabilities       → tools\nReusable behavior  → skills\nHard boundaries    → policies\nSystem guarantees  → hooks\nExternal events    → triggers\nUser interface     → thin surface\n```\n\nThis is what the Antigravity SDK made pleasant. I could build one agent runtime and reuse it across terminal, Telegram, and deck generation. I could keep the tutoring behavior in `SKILL.md`\n\nfiles instead of duplicating it. I could wrap real side effects with policies and hooks instead of trusting the model to behave.\n\nThe Anki tutor is just the concrete example.\n\nThe pattern generalizes.\n\nA support agent could keep triage behavior in a skill, expose ticket updates as tools, deny destructive writes by policy, and audit every status change by hook.\n\nA code review agent could keep review rubrics in skills, expose GitHub as tools, require approval before comments, and verify every posted review.\n\nA research agent could keep extraction protocols in skills, use file triggers to process new papers, and write structured outputs only after validation.\n\nThe skill is the portable behavior module.\n\nThe SDK is the harness that lets it act.\n\nI started this because I was too lazy to open Anki.\n\nThat sounds like a joke, but most useful automation starts there. Not with a grand platform vision. With a small workflow that keeps not happening because the friction is just high enough.\n\nThe surprising part was not that an LLM could quiz me.\n\nThe surprising part was how clean the architecture became.\n\nTools gave the agent hands. Skills gave it habits. Policies gave it boundaries. Hooks gave it receipts. Triggers made it wake up when something changed.\n\nThat is the version of agents I trust more: not one giant prompt pretending to be an application, but a small runtime with clear layers.\n\nThe future of agent apps is not monolithic complex systems.\n\nIt is smaller prompts, sharper tools, reusable skills, and a harness that refuses to let the model pretend a side effect happened when it did not.", "url": "https://wpnews.pro/news/skills-over-system-prompts-building-an-anki-tutor-with-the-antigravity-sdk", "canonical_source": "https://dev.to/gde/skills-over-system-prompts-building-an-anki-tutor-with-the-antigravity-sdk-2o8f", "published_at": "2026-06-19 10:09:03+00:00", "updated_at": "2026-06-19 10:37:19.413844+00:00", "lang": "en", "topics": ["ai-agents", "developer-tools", "large-language-models"], "entities": ["Anki", "Google Antigravity SDK", "Gemini", "Google AI Studio", "AnkiConnect", "Telegram"], "alternates": {"html": "https://wpnews.pro/news/skills-over-system-prompts-building-an-anki-tutor-with-the-antigravity-sdk", "markdown": "https://wpnews.pro/news/skills-over-system-prompts-building-an-anki-tutor-with-the-antigravity-sdk.md", "text": "https://wpnews.pro/news/skills-over-system-prompts-building-an-anki-tutor-with-the-antigravity-sdk.txt", "jsonld": "https://wpnews.pro/news/skills-over-system-prompts-building-an-anki-tutor-with-the-antigravity-sdk.jsonld"}}